diff --git a/app/Base.java b/app/Base.java index 2bee7b3d9..4b8e6e016 100644 --- a/app/Base.java +++ b/app/Base.java @@ -42,6 +42,7 @@ import javax.swing.undo.*; import com.apple.mrj.*; import com.ice.jni.registry.*; +import processing.core.*; /** @@ -52,21 +53,8 @@ import com.ice.jni.registry.*; * files and images, etc) that comes from that. */ public class Base { - static final int VERSION = 3; + static final int VERSION = 10; static final String VERSION_NAME = "0010 Alpha"; - - // platform IDs for PApplet.platform - - static final int WINDOWS = 1; - static final int MACOS9 = 2; - static final int MACOSX = 3; - static final int LINUX = 4; - static final int OTHER = 0; - - // used by split, all the standard whitespace chars - // (uncludes unicode nbsp, that little bostage) - - static final String WHITESPACE = " \t\n\r\f\u00A0"; /** * Path of filename opened on the command line, @@ -76,79 +64,18 @@ public class Base { Editor editor; - /** - * "1.3" or "1.1" or whatever (just the first three chars) - */ - public static final String javaVersionName = - System.getProperty("java.version").substring(0,3); - - /** - * Version of Java that's in use, whether 1.1 or 1.3 or whatever, - * stored as a float. - *

- * Note that because this is stored as a float, the values may - * not be exactly 1.3 or 1.4. Instead, make sure you're - * comparing against 1.3f or 1.4f, which will have the same amount - * of error (i.e. 1.40000001). This could just be a double, but - * since Processing only uses floats, it's safer to do this, - * because there's no good way to specify a double with the preproc. - */ - public static final float javaVersion = - new Float(javaVersionName).floatValue(); - - /** - * Current platform in use, one of the - * PConstants WINDOWS, MACOSX, MACOS9, LINUX or OTHER. - */ - static public int platform; - - /** - * Current platform in use. - *

- * Equivalent to System.getProperty("os.name"), just used internally. - */ - static public String platformName = - System.getProperty("os.name"); - - static { - // figure out which operating system - // this has to be first, since editor needs to know - - if (platformName.toLowerCase().indexOf("mac") != -1) { - // can only check this property if running on a mac - // on a pc it throws a security exception and kills the applet - // (but on the mac it does just fine) - if (System.getProperty("mrj.version") != null) { // running on a mac - platform = (platformName.equals("Mac OS X")) ? - MACOSX : MACOS9; - } - - } else { - String osname = System.getProperty("os.name"); - - if (osname.indexOf("Windows") != -1) { - platform = WINDOWS; - - } else if (osname.equals("Linux")) { // true for the ibm vm - platform = LINUX; - - } else { - platform = OTHER; - } - } - } static public void main(String args[]) { // make sure that this is running on java 1.4 - //if (PApplet.javaVersion < 1.4f) { + if (PApplet.javaVersion < 1.4f) { //System.err.println("no way man"); - // Base.showError("Need to install Java 1.4", - // "This version of Arduino requires \n" + - // "Java 1.4 or later to run properly.\n" + - // "Please visit java.com to upgrade.", null); - // } + Base.showError("Need to install Java 1.4", + "This version of Processing requires \n" + + "Java 1.4 or later to run properly.\n" + + "Please visit java.com to upgrade.", null); + } // grab any opened file from the command line @@ -183,14 +110,34 @@ public class Base { // set the look and feel before opening the window try { - if (Base.isLinux()) { - // linux is by default (motif?) even uglier than metal - // actually, i'm using native menus, so they're ugly and - // motif-looking. ick. need to fix this. - UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + if (Base.isMacOS()) { + // Use the Quaqua L & F on OS X to make JFileChooser less awful + UIManager.setLookAndFeel("ch.randelshofer.quaqua.QuaquaLookAndFeel"); + // undo quaqua trying to fix the margins, since we've already + // hacked that in, bit by bit, over the years + UIManager.put("Component.visualMargin", new Insets(1, 1, 1, 1)); + + } else if (Base.isLinux()) { + // Linux is by default even uglier than metal (Motif?). + // Actually, i'm using native menus, so they're even uglier + // and Motif-looking (Lesstif?). Ick. Need to fix this. + //String lfname = UIManager.getCrossPlatformLookAndFeelClassName(); + //UIManager.setLookAndFeel(lfname); + + // For 0120, trying out the gtk+ look and feel as the default. + // This is available in Java 1.4.2 and later, and it can't possibly + // be any worse than Metal. (Ocean might also work, but that's for + // Java 1.5, and we aren't going there yet) + UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); + } else { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } + //} catch (ClassNotFoundException cnfe) { + // just default to the native look and feel for this platform + // i.e. appears that some linux systems don't have the gtk l&f + //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { e.printStackTrace(); } @@ -228,7 +175,7 @@ public class Base { * specifically a Mac OS X machine because it doesn't un on OS 9 anymore. */ static public boolean isMacOS() { - return platform == MACOSX; + return PApplet.platform == PConstants.MACOSX; } @@ -236,7 +183,7 @@ public class Base { * returns true if running on windows. */ static public boolean isWindows() { - return platform == WINDOWS; + return PApplet.platform == PConstants.WINDOWS; } @@ -244,7 +191,7 @@ public class Base { * true if running on linux. */ static public boolean isLinux() { - return platform == LINUX; + return PApplet.platform == PConstants.LINUX; } @@ -472,8 +419,9 @@ public class Base { sketchbookFolder = new File(documentsFolder, "Arduino"); } catch (Exception e) { - showError("sketch folder problem", - "Could not locate default sketch folder location.", e); + //showError("Could not find folder", + // "Could not locate the Documents folder.", e); + sketchbookFolder = promptSketchbookLocation(); } } else if (isWindows()) { @@ -502,11 +450,15 @@ public class Base { sketchbookFolder = new File(personalPath, "Arduino"); } catch (Exception e) { - showError("Problem getting documents folder", - "Error getting the Arduino sketchbook folder.", e); + //showError("Problem getting folder", + // "Could not locate the Documents folder.", e); + sketchbookFolder = promptSketchbookLocation(); } } else { + sketchbookFolder = promptSketchbookLocation(); + + /* // on linux (or elsewhere?) prompt the user for the location JFileChooser fc = new JFileChooser(); fc.setDialogTitle("Select the folder where " + @@ -523,6 +475,7 @@ public class Base { } else { System.exit(0); } + */ } // create the folder if it doesn't exist already @@ -551,6 +504,68 @@ public class Base { } + /** + * Check for a new sketchbook location. + */ + static protected File promptSketchbookLocation() { + File folder = null; + + folder = new File(System.getProperty("user.home"), "sketchbook"); + if (!folder.exists()) { + folder.mkdirs(); + return folder; + } + + folder = Base.selectFolder("Select (or create new) folder for sketches...", + null, null); + if (folder == null) { + System.exit(0); + } + return folder; + } + + + /** + * Implementation for choosing directories that handles both the + * Mac OS X hack to allow the native AWT file dialog, or uses + * the JFileChooser on other platforms. Mac AWT trick obtained from + * this post + * on the OS X Java dev archive which explains the cryptic note in + * Apple's Java 1.4 release docs about the special System property. + */ + static public File selectFolder(String prompt, File folder, Frame frame) { + if (Base.isMacOS()) { + if (frame == null) frame = new Frame(); //.pack(); + FileDialog fd = new FileDialog(frame, prompt, FileDialog.LOAD); + if (folder != null) { + fd.setDirectory(folder.getParent()); + //fd.setFile(folder.getName()); + } + System.setProperty("apple.awt.fileDialogForDirectories", "true"); + fd.show(); + System.setProperty("apple.awt.fileDialogForDirectories", "false"); + if (fd.getFile() == null) { + return null; + } + return new File(fd.getDirectory(), fd.getFile()); + + } else { + JFileChooser fc = new JFileChooser(); + fc.setDialogTitle(prompt); + if (folder != null) { + fc.setSelectedFile(folder); + } + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + int returned = fc.showOpenDialog(new JDialog()); + if (returned == JFileChooser.APPROVE_OPTION) { + return fc.getSelectedFile(); + } + } + return null; + } + + static public String cleanKey(String what) { // jnireg seems to be reading the chars as bytes // so maybe be as simple as & 0xff and then running through decoder @@ -631,20 +646,44 @@ public class Base { // ................................................................. - /** - * Given the reference filename from the keywords list, - * builds a URL and passes it to openURL. - */ static public void showReference(String referenceFile) { - String currentDir = System.getProperty("user.dir"); - openURL(currentDir + File.separator + - "reference" + File.separator + - referenceFile + ".html"); + openURL(Base.getContents("reference" + File.separator + referenceFile)); + } + + + static public void showReference() { + showReference("index.html"); + } + + + static public void showEnvironment() { + showReference("Guide_Environment.html"); + } + + + static public void showTroubleshooting() { + showReference("Guide_Troubleshooting.html"); } + /** + * Opens the local copy of the FAQ that's included + * with the Processing download. + */ + static public void showFAQ() { + showReference("faq.html"); + } + + + // ................................................................. + + /** * Implements the cross-platform headache of opening URLs + * TODO This code should be replaced by PApplet.link(), + * however that's not a static method (because it requires + * an AppletContext when used as an applet), so it's mildly + * trickier than just removing this method. */ static public void openURL(String url) { //System.out.println("opening url " + url); @@ -707,11 +746,19 @@ public class Base { } else if (Base.isLinux()) { // how's mozilla sound to ya, laddie? //Runtime.getRuntime().exec(new String[] { "mozilla", url }); - String browser = Preferences.get("browser"); - Runtime.getRuntime().exec(new String[] { browser, url }); - + //String browser = Preferences.get("browser"); + //Runtime.getRuntime().exec(new String[] { browser, url }); + String launcher = Preferences.get("launcher.linux"); + if (launcher != null) { + Runtime.getRuntime().exec(new String[] { launcher, url }); + } } else { - System.err.println("unspecified platform"); + String launcher = Preferences.get("launcher"); + if (launcher != null) { + Runtime.getRuntime().exec(new String[] { launcher, url }); + } else { + System.err.println("Unspecified platform, no launcher available."); + } } } catch (IOException e) { @@ -721,6 +768,36 @@ public class Base { } + static boolean openFolderAvailable() { + if (Base.isWindows() || Base.isMacOS()) return true; + + if (Base.isLinux()) { + // Assume that this is set to something valid + if (Preferences.get("launcher.linux") != null) { + return true; + } + + // Attempt to use gnome-open + try { + Process p = Runtime.getRuntime().exec(new String[] { "gnome-open" }); + int result = p.waitFor(); + // Not installed will throw an IOException (JDK 1.4.2, Ubuntu 7.04) + Preferences.set("launcher.linux", "gnome-open"); + return true; + } catch (Exception e) { } + + // Attempt with kde-open + try { + Process p = Runtime.getRuntime().exec(new String[] { "kde-open" }); + int result = p.waitFor(); + Preferences.set("launcher.linux", "kde-open"); + return true; + } catch (Exception e) { } + } + return false; + } + + /** * Implements the other cross-platform headache of opening * a folder in the machine's native file browser. @@ -742,6 +819,11 @@ public class Base { } else if (Base.isMacOS()) { openURL(folder); // handles char replacement, etc + } else if (Base.isLinux()) { + String launcher = Preferences.get("launcher.linux"); + if (launcher != null) { + Runtime.getRuntime().exec(new String[] { launcher, folder }); + } } } catch (IOException e) { e.printStackTrace(); @@ -780,7 +862,7 @@ public class Base { * for errors that allow P5 to continue running. */ static public void showError(String title, String message, - Exception e) { + Throwable e) { if (title == null) title = "Error"; JOptionPane.showMessageDialog(new Frame(), message, title, JOptionPane.ERROR_MESSAGE); @@ -793,23 +875,29 @@ public class Base { // ................................................................... + static public String getContents(String what) { + String basePath = System.getProperty("user.dir"); + /* + // do this later, when moving to .app package + if (PApplet.platform == PConstants.MACOSX) { + basePath = System.getProperty("processing.contents"); + } + */ + return basePath + File.separator + what; + } + + + static public String getLibContents(String what) { + return getContents("lib" + File.separator + what); + } + + static public Image getImage(String name, Component who) { Image image = null; Toolkit tk = Toolkit.getDefaultToolkit(); - //if ((Base.platform == Base.MACOSX) || - //(Base.platform == Base.MACOS9)) { - image = tk.getImage("lib/" + name); - //} else { - //image = tk.getImage(who.getClass().getResource(name)); - //} - - //image = tk.getImage("lib/" + name); - //URL url = PdeApplet.class.getResource(name); - //image = tk.getImage(url); - //} - //MediaTracker tracker = new MediaTracker(applet); - MediaTracker tracker = new MediaTracker(who); //frame); + image = tk.getImage(getLibContents(name)); + MediaTracker tracker = new MediaTracker(who); tracker.addImage(image, 0); try { tracker.waitForAll(); @@ -819,17 +907,7 @@ public class Base { static public InputStream getStream(String filename) throws IOException { - //if (Base.platform == Base.MACOSX) { - // macos doesn't seem to think that files in the lib folder - // are part of the resources, unlike windows or linux. - // actually, this is only the case when running as a .app, - // since it works fine from run.sh, but not Arduino.app - return new FileInputStream("lib/" + filename); - //} - - // all other, more reasonable operating systems - //return cls.getResource(filename).openStream(); - //return Base.class.getResource(filename).openStream(); + return new FileInputStream(getLibContents(filename)); } @@ -965,7 +1043,7 @@ public class Base { if (!Preferences.getBoolean("compiler.save_build_files")) { if (!dead.delete()) { // temporarily disabled - //System.err.println("couldn't delete " + dead); + System.err.println("Could not delete " + dead); } } } else { @@ -1040,197 +1118,4 @@ public class Base { } } } - - - /** - * Equivalent to the one in PApplet, but static (die() is removed) - */ - static public String[] loadStrings(File file) { - try { - FileInputStream input = new FileInputStream(file); - BufferedReader reader = - new BufferedReader(new InputStreamReader(input)); - - String lines[] = new String[100]; - int lineCount = 0; - String line = null; - while ((line = reader.readLine()) != null) { - if (lineCount == lines.length) { - String temp[] = new String[lineCount << 1]; - System.arraycopy(lines, 0, temp, 0, lineCount); - lines = temp; - } - lines[lineCount++] = line; - } - reader.close(); - - if (lineCount == lines.length) { - return lines; - } - - // resize array to appropraite amount for these lines - String output[] = new String[lineCount]; - System.arraycopy(lines, 0, output, 0, lineCount); - return output; - - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - - ////////////////////////////////////////////////////////////// - - // STRINGS - - - /** - * Remove whitespace characters from the beginning and ending - * of a String. Works like String.trim() but includes the - * unicode nbsp character as well. - */ - static public String trim(String str) { - return str.replace('\u00A0', ' ').trim(); - - /* - int left = 0; - int right = str.length() - 1; - - while ((left <= right) && - (WHITESPACE.indexOf(str.charAt(left)) != -1)) left++; - if (left == right) return ""; - - while (WHITESPACE.indexOf(str.charAt(right)) != -1) --right; - - return str.substring(left, right-left+1); - */ - } - - /** - * Join an array of Strings together as a single String, - * separated by the whatever's passed in for the separator. - */ - static public String join(String str[], char separator) { - return join(str, String.valueOf(separator)); - } - - - /** - * Join an array of Strings together as a single String, - * separated by the whatever's passed in for the separator. - *

- * To use this on numbers, first pass the array to nf() or nfs() - * to get a list of String objects, then use join on that. - *

-   * e.g. String stuff[] = { "apple", "bear", "cat" };
-   *      String list = join(stuff, ", ");
-   *      // list is now "apple, bear, cat"
- */ - static public String join(String str[], String separator) { - StringBuffer buffer = new StringBuffer(); - for (int i = 0; i < str.length; i++) { - if (i != 0) buffer.append(separator); - buffer.append(str[i]); - } - return buffer.toString(); - } - - - /** - * Split the provided String at wherever whitespace occurs. - * Multiple whitespace (extra spaces or tabs or whatever) - * between items will count as a single break. - *

- * The whitespace characters are "\t\n\r\f", which are the defaults - * for java.util.StringTokenizer, plus the unicode non-breaking space - * character, which is found commonly on files created by or used - * in conjunction with Mac OS X (character 160, or 0x00A0 in hex). - *

-   * i.e. split("a b") -> { "a", "b" }
-   *      split("a    b") -> { "a", "b" }
-   *      split("a\tb") -> { "a", "b" }
-   *      split("a \t  b  ") -> { "a", "b" }
- */ - static public String[] split(String what) { - return split(what, WHITESPACE); - } - - - /** - * Splits a string into pieces, using any of the chars in the - * String 'delim' as separator characters. For instance, - * in addition to white space, you might want to treat commas - * as a separator. The delimeter characters won't appear in - * the returned String array. - *
-   * i.e. split("a, b", " ,") -> { "a", "b" }
-   * 
- * To include all the whitespace possibilities, use the variable - * WHITESPACE, found in PConstants: - *
-   * i.e. split("a   | b", WHITESPACE + "|");  ->  { "a", "b" }
- */ - static public String[] split(String what, String delim) { - StringTokenizer toker = new StringTokenizer(what, delim); - String pieces[] = new String[toker.countTokens()]; - - int index = 0; - while (toker.hasMoreTokens()) { - pieces[index++] = toker.nextToken(); - } - return pieces; - } - - - /** - * Split a string into pieces along a specific character. - * Most commonly used to break up a String along tab characters. - *

- * This operates differently than the others, where the - * single delimeter is the only breaking point, and consecutive - * delimeters will produce an empty string (""). This way, - * one can split on tab characters, but maintain the column - * alignments (of say an excel file) where there are empty columns. - */ - static public String[] split(String what, char delim) { - // do this so that the exception occurs inside the user's - // program, rather than appearing to be a bug inside split() - if (what == null) return null; - //return split(what, String.valueOf(delim)); // huh - - char chars[] = what.toCharArray(); - int splitCount = 0; //1; - for (int i = 0; i < chars.length; i++) { - if (chars[i] == delim) splitCount++; - } - // make sure that there is something in the input string - //if (chars.length > 0) { - // if the last char is a delimeter, get rid of it.. - //if (chars[chars.length-1] == delim) splitCount--; - // on second thought, i don't agree with this, will disable - //} - if (splitCount == 0) { - String splits[] = new String[1]; - splits[0] = new String(what); - return splits; - } - //int pieceCount = splitCount + 1; - String splits[] = new String[splitCount + 1]; - int splitIndex = 0; - int startIndex = 0; - for (int i = 0; i < chars.length; i++) { - if (chars[i] == delim) { - splits[splitIndex++] = - new String(chars, startIndex, i-startIndex); - startIndex = i + 1; - } - } - //if (startIndex != chars.length) { - splits[splitIndex] = - new String(chars, startIndex, chars.length-startIndex); - //} - return splits; - } - } diff --git a/app/Editor.java b/app/Editor.java index e2c9955cc..316104d5d 100644 --- a/app/Editor.java +++ b/app/Editor.java @@ -28,11 +28,13 @@ package processing.app; import processing.app.syntax.*; import processing.app.tools.*; +import processing.core.*; import java.awt.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.awt.event.*; +import java.awt.print.*; import java.io.*; import java.lang.reflect.*; import java.net.*; @@ -45,7 +47,9 @@ import javax.swing.event.*; import javax.swing.text.*; import javax.swing.undo.*; +import com.apple.mrj.*; import com.oroinc.text.regex.*; +//import de.hunsicker.jalopy.*; import com.apple.mrj.*; import gnu.io.*; @@ -78,6 +82,9 @@ public class Editor extends JFrame boolean handleNewShift; boolean handleNewLibrary; + PageFormat pageFormat; + PrinterJob printerJob; + EditorButtons buttons; EditorHeader header; EditorStatus status; @@ -133,14 +140,12 @@ public class Editor extends JFrame // used internally, and only briefly CompoundEdit compoundEdit; - //static public UndoManager undo = new UndoManager(); // editor needs this guy - // //SketchHistory history; // TODO re-enable history Sketchbook sketchbook; //Preferences preferences; - //FindReplace find; + FindReplace find; //static Properties keywords; // keyword -> reference html lookup @@ -167,9 +172,13 @@ public class Editor extends JFrame // add listener to handle window close box hit event addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { - handleQuit(); + handleQuitInternal(); } }); + // don't close the window when clicked, the app will take care + // of that via the handleQuitInternal() methods + // http://dev.processing.org/bugs/show_bug.cgi?id=440 + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); PdeKeywords keywords = new PdeKeywords(); sketchbook = new Sketchbook(this); @@ -188,8 +197,14 @@ public class Editor extends JFrame // doesn't matter when this is created, just make it happen at some point //find = new FindReplace(Editor.this); - Container pain = getContentPane(); + //Container pain = getContentPane(); + //pain.setLayout(new BorderLayout()); + // for rev 0120, placing things inside a JPanel because + Container contentPain = getContentPane(); + contentPain.setLayout(new BorderLayout()); + JPanel pain = new JPanel(); pain.setLayout(new BorderLayout()); + contentPain.add(pain, BorderLayout.CENTER); Box box = Box.createVerticalBox(); Box upper = Box.createVerticalBox(); @@ -251,6 +266,17 @@ public class Editor extends JFrame listener = new EditorListener(this, textarea); pain.add(box); + pain.setTransferHandler(new TransferHandler() { + + public boolean canImport(JComponent dest, DataFlavor[] flavors) { + // claim that we can import everything + return true; + } + + public boolean importData(JComponent src, Transferable transferable) { + DataFlavor[] flavors = transferable.getTransferDataFlavors(); + + /* DropTarget dt = new DropTarget(this, new DropTargetListener() { public void dragEnter(DropTargetDragEvent event) { @@ -278,14 +304,17 @@ public class Editor extends JFrame Transferable transferable = event.getTransferable(); DataFlavor flavors[] = transferable.getTransferDataFlavors(); + */ int successful = 0; for (int i = 0; i < flavors.length; i++) { try { //System.out.println(flavors[i]); //System.out.println(transferable.getTransferData(flavors[i])); - java.util.List list = - (java.util.List) transferable.getTransferData(flavors[i]); + Object stuff = transferable.getTransferData(flavors[i]); + if (!(stuff instanceof java.util.List)) continue; + java.util.List list = (java.util.List) stuff; + for (int j = 0; j < list.size(); j++) { Object item = list.get(j); if (item instanceof File) { @@ -298,7 +327,7 @@ public class Editor extends JFrame File parent = file.getParentFile(); if (name.equals(parent.getName())) { handleOpenFile(file); - return; + return true; } } @@ -310,6 +339,7 @@ public class Editor extends JFrame } catch (Exception e) { e.printStackTrace(); + return false; } } @@ -322,6 +352,7 @@ public class Editor extends JFrame } else { message(successful + " files added to the sketch."); } + return true; } }); } @@ -488,7 +519,9 @@ public class Editor extends JFrame listener.applyPreferences(); // in case moved to a new location - sketchbook.rebuildMenus(); + // For 0125, changing to async version (to be implemented later) + //sketchbook.rebuildMenus(); + sketchbook.rebuildMenusAsync(); } @@ -576,11 +609,19 @@ public class Editor extends JFrame menu.addSeparator(); item = newJMenuItem("Page Setup", 'P', true); - item.setEnabled(false); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handlePageSetup(); + } + }); menu.add(item); item = newJMenuItem("Print", 'P'); - item.setEnabled(false); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handlePrint(); + } + }); menu.add(item); // macosx already has its own preferences and quit menu @@ -600,7 +641,7 @@ public class Editor extends JFrame item = newJMenuItem("Quit", 'Q'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleQuit(); + handleQuitInternal(); } }); menu.add(item); @@ -639,17 +680,9 @@ public class Editor extends JFrame menu.addSeparator(); - item = new JMenuItem("Add File..."); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - sketch.addFile(); - } - }); - menu.add(item); - menu.add(sketchbook.getImportMenu()); - if (Base.isWindows() || Base.isMacOS()) { + //if (Base.isWindows() || Base.isMacOS()) { // no way to do an 'open in file browser' on other platforms // since there isn't any sort of standard item = newJMenuItem("Show Sketch Folder", 'K', false); @@ -660,8 +693,20 @@ public class Editor extends JFrame } }); menu.add(item); + if (!Base.openFolderAvailable()) { + item.setEnabled(false); } + //menu.addSeparator(); + + item = new JMenuItem("Add File..."); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + sketch.addFile(); + } + }); + menu.add(item); + // TODO re-enable history //history.attachMenu(menu); return menu; @@ -681,20 +726,39 @@ public class Editor extends JFrame item.addActionListener(new ActionListener() { synchronized public void actionPerformed(ActionEvent e) { new AutoFormat(Editor.this).show(); - //handleBeautify(); + + /* + Jalopy jalopy = new Jalopy(); + jalopy.setInput(getText(), sketch.current.file.getAbsolutePath()); + StringBuffer buffer = new StringBuffer(); + jalopy.setOutput(buffer); + jalopy.setInspect(false); + jalopy.format(); + setText(buffer.toString(), 0, 0); + + if (jalopy.getState() == Jalopy.State.OK) + System.out.println("successfully formatted"); + else if (jalopy.getState() == Jalopy.State.WARN) + System.out.println(" formatted with warnings"); + else if (jalopy.getState() == Jalopy.State.ERROR) + System.out.println(" could not be formatted"); + */ } }); menu.add(item); - /*item = new JMenuItem("Create Font..."); + item = new JMenuItem("Copy for Discourse"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - //new CreateFont().show(sketch.dataFolder); - new CreateFont(Editor.this).show(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + new DiscourseFormat(Editor.this).show(); + } + }); } }); menu.add(item); - */ + item = new JMenuItem("Archive Sketch"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -706,13 +770,17 @@ public class Editor extends JFrame }); menu.add(item); + /* item = new JMenuItem("Export Folder..."); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { new ExportFolder(Editor.this).show(); } }); menu.add(item); + */ menu.addSeparator(); mcuMenu = new JMenu("Microcontroller (MCU)"); @@ -946,21 +1014,18 @@ public class Editor extends JFrame menu.add(item); } - item = new JMenuItem("Troubleshooting"); + item = new JMenuItem("Environment"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Base.openURL(System.getProperty("user.dir") + File.separator + - "reference" + File.separator + - "Guide_Troubleshooting.html"); + Base.showEnvironment(); } }); menu.add(item); - item = new JMenuItem("Environment"); + item = new JMenuItem("Troubleshooting"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Base.openURL(System.getProperty("user.dir") + File.separator + - "reference" + File.separator + "Guide_Environment.html"); + Base.showTroubleshooting(); } }); menu.add(item); @@ -968,8 +1033,17 @@ public class Editor extends JFrame item = new JMenuItem("Reference"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Base.openURL(System.getProperty("user.dir") + File.separator + - "reference" + File.separator + "index.html"); + Base.showReference(); + } + }); + menu.add(item); + + item = newJMenuItem("Find in Reference", 'F', true); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (textarea.isSelectionActive()) { + handleReference(); + } } }); menu.add(item); @@ -977,33 +1051,11 @@ public class Editor extends JFrame item = new JMenuItem("Frequently Asked Questions"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Base.openURL(System.getProperty("user.dir") + File.separator + - "reference" + File.separator + "FAQ.html"); + Base.showFAQ(); } }); menu.add(item); -// item = newJMenuItem("Find in Reference", 'F', true); -// item.addActionListener(new ActionListener() { -// public void actionPerformed(ActionEvent e) { -// if (textarea.isSelectionActive()) { -// String text = textarea.getSelectedText(); -// if (text.length() == 0) { -// message("First select a word to find in the reference."); -// -// } else { -// String referenceFile = PdeKeywords.getReference(text); -// if (referenceFile == null) { -// message("No reference available for \"" + text + "\""); -// } else { -// Base.showReference(referenceFile); -// } -// } -// } -// } -// }); -// menu.add(item); - item = newJMenuItem("Visit www.arduino.cc", '5'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -1083,22 +1135,27 @@ public class Editor extends JFrame item = newJMenuItem("Find...", 'F'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - new FindReplace(Editor.this).show(); - //find.show(); + if (find == null) { + find = new FindReplace(Editor.this); + } + //new FindReplace(Editor.this).show(); + find.show(); //find.setVisible(true); } }); menu.add(item); + // TODO find next should only be enabled after a + // search has actually taken place item = newJMenuItem("Find Next", 'G'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - // TODO find next should only be enabled after a - // search has actually taken place + if (find != null) { //find.find(true); - FindReplace find = new FindReplace(Editor.this); //.show(); + //FindReplace find = new FindReplace(Editor.this); //.show(); find.find(true); } + } }); menu.add(item); @@ -1116,9 +1173,9 @@ public class Editor extends JFrame /** * A software engineer, somewhere, needs to have his abstraction - * taken away. In some countries they jail people for writing the - * sort of crappy api that would require a four line helper function - * to set the command key for a menu item. + * taken away. In some countries they jail or beat people for writing + * the sort of API that would require a five line helper function + * just to set the command key for a menu item. */ static public JMenuItem newJMenuItem(String title, int what, boolean shift) { @@ -1191,7 +1248,6 @@ public class Editor extends JFrame protected void updateRedoState() { if (undo.canRedo()) { - //this.setEnabled(true); redoItem.setEnabled(true); redoItem.setText(undo.getRedoPresentationName()); putValue(Action.NAME, undo.getRedoPresentationName()); @@ -1339,10 +1395,12 @@ public class Editor extends JFrame redoAction.updateRedoState(); } + public void beginCompoundEdit() { compoundEdit = new CompoundEdit(); } + public void endCompoundEdit() { compoundEdit.end(); undo.addEdit(compoundEdit); @@ -1352,6 +1410,8 @@ public class Editor extends JFrame } + // ................................................................... + public void handleRun(final boolean present) { doClose(); @@ -1576,42 +1636,71 @@ public class Editor extends JFrame String prompt = "Save changes to " + sketch.name + "? "; if (checkModifiedMode != HANDLE_QUIT) { - // if the user is not quitting, then use the nicer + // if the user is not quitting, then use simpler nicer // dialog that's actually inside the p5 window. status.prompt(prompt); } else { - // if the user selected quit, then this has to be done with - // a JOptionPane instead of internally in the editor. - // TODO this is actually just a bug to be fixed. - - // macosx java kills the app even though cancel might get hit - // so the cancel button is (temporarily) left off - // this may be treated differently in macosx java 1.4, - // but 1.4 isn't currently stable enough to use. - - // turns out windows has the same problem (sometimes) - // disable cancel for now until a fix can be found. - - Object[] options = { "Yes", "No" }; - int result = JOptionPane.showOptionDialog(this, - prompt, - "Quit", - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); + if (!Base.isMacOS() || PApplet.javaVersion < 1.5f) { + int result = + JOptionPane.showConfirmDialog(this, prompt, "Quit", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); if (result == JOptionPane.YES_OPTION) { handleSave(true); checkModified2(); } else if (result == JOptionPane.NO_OPTION) { - checkModified2(); // though this may just quit + checkModified2(); + } + // cancel is ignored altogether - } else if (result == JOptionPane.CANCEL_OPTION) { - // ignored + } else { + // This code is disabled unless Java 1.5 is being used on Mac OS X + // because of a Java bug that prevents the initial value of the + // dialog from being set properly (at least on my MacBook Pro). + // The bug causes the "Don't Save" option to be the highlighted, + // blinking, default. This sucks. But I'll tell you what doesn't + // suck--workarounds for the Mac and Apple's snobby attitude about it! + + // adapted from the quaqua guide + // http://www.randelshofer.ch/quaqua/guide/joptionpane.html + JOptionPane pane = + new JOptionPane(" " + + " " + + "Do you want to save changes to this sketch
" + + " before closing?
" + + "

If you don't save, your changes will be lost.", + JOptionPane.QUESTION_MESSAGE); + + String[] options = new String[] { + "Save", "Cancel", "Don't Save" + }; + pane.setOptions(options); + + // highlight the safest option ala apple hig + pane.setInitialValue(options[0]); + + // on macosx, setting the destructive property places this option + // away from the others at the lefthand side + pane.putClientProperty("Quaqua.OptionPane.destructiveOption", + new Integer(2)); + + JDialog dialog = pane.createDialog(this, null); + dialog.show(); + + Object result = pane.getValue(); + if (result == options[0]) { // save (and quit) + handleSave(true); + checkModified2(); + + } else if (result == options[2]) { // don't save (still quit) + checkModified2(); + } } } } @@ -1739,9 +1828,15 @@ public class Editor extends JFrame * Open a sketch from a particular path, but don't check to save changes. * Used by Sketch.saveAs() to re-open a sketch after the "Save As" */ - public void handleOpenUnchecked(String path) { + public void handleOpenUnchecked(String path, int codeIndex, + int selStart, int selStop, int scrollPos) { doClose(); handleOpen2(path); + + sketch.setCurrent(codeIndex); + textarea.select(selStart, selStop); + //textarea.updateScrollBars(); + textarea.setScrollPosition(scrollPos); } @@ -1761,7 +1856,8 @@ public class Editor extends JFrame if (!oldPath.equals(newPath)) { if (Base.calcFolderSize(sketch.folder) == 0) { Base.removeDir(sketch.folder); - sketchbook.rebuildMenus(); + //sketchbook.rebuildMenus(); + sketchbook.rebuildMenusAsync(); } } } catch (Exception e) { } // oh well @@ -1893,7 +1989,11 @@ public class Editor extends JFrame message(EMPTY); } // rebuild sketch menu in case a save-as was forced - sketchbook.rebuildMenus(); + // Disabling this for 0125, instead rebuild the menu inside + // the Save As method of the Sketch object, since that's the + // only one who knows whether something was renamed. + //sketchbook.rebuildMenus(); + //sketchbook.rebuildMenusAsync(); } catch (Exception e) { // show the error as a message in the window @@ -1918,7 +2018,10 @@ public class Editor extends JFrame try { if (sketch.saveAs()) { message("Done Saving."); - sketchbook.rebuildMenus(); + // Disabling this for 0125, instead rebuild the menu inside + // the Save As method of the Sketch object, since that's the + // only one who knows whether something was renamed. + //sketchbook.rebuildMenusAsync(); } else { message("Save Cancelled."); } @@ -2007,6 +2110,7 @@ public class Editor extends JFrame null, options, options[0]); + if (result == JOptionPane.OK_OPTION) { handleSave(true); @@ -2021,13 +2125,57 @@ public class Editor extends JFrame return true; } - + + public void handlePageSetup() { + //printerJob = null; + if (printerJob == null) { + printerJob = PrinterJob.getPrinterJob(); + } + if (pageFormat == null) { + pageFormat = printerJob.defaultPage(); + } + pageFormat = printerJob.pageDialog(pageFormat); + //System.out.println("page format is " + pageFormat); + } + + + public void handlePrint() { + message("Printing..."); + //printerJob = null; + if (printerJob == null) { + printerJob = PrinterJob.getPrinterJob(); + } + if (pageFormat != null) { + //System.out.println("setting page format " + pageFormat); + printerJob.setPrintable(textarea.getPainter(), pageFormat); + } else { + printerJob.setPrintable(textarea.getPainter()); + } + // set the name of the job to the code name + printerJob.setJobName(sketch.current.name); + + if (printerJob.printDialog()) { + try { + printerJob.print(); + message("Done printing."); + + } catch (PrinterException pe) { + error("Error while printing."); + pe.printStackTrace(); + } + } else { + message("Printing canceled."); + } + //printerJob = null; // clear this out? + } + + /** * Quit, but first ask user if it's ok. Also store preferences * to disk just in case they want to quit. Final exit() happens * in Editor since it has the callback from EditorStatus. */ - public void handleQuit() { + public void handleQuitInternal() { // doStop() isn't sufficient with external vm & quit // instead use doClose() which will kill the external vm doClose(); @@ -2036,6 +2184,27 @@ public class Editor extends JFrame } + /** + * Method for the MRJQuitHandler, needs to be dealt with differently + * than the regular handler because OS X has an annoying implementation + * quirk + * that requires an exception to be thrown in order to properly cancel + * a quit message. + */ + public void handleQuit() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + handleQuitInternal(); + } + }); + + // Throw IllegalStateException so new thread can execute. + // If showing dialog on this thread in 10.2, we would throw + // upon JOptionPane.NO_OPTION + throw new IllegalStateException("Quit Pending User Confirmation"); + } + + /** * Actually do the quit action. */ @@ -2050,6 +2219,26 @@ public class Editor extends JFrame System.exit(0); } + + + protected void handleReference() { + String text = textarea.getSelectedText().trim(); + + if (text.length() == 0) { + message("First select a word to find in the reference."); + + } else { + String referenceFile = PdeKeywords.getReference(text); + //System.out.println("reference file is " + referenceFile); + if (referenceFile == null) { + message("No reference available for \"" + text + "\""); + } else { + Base.showReference(referenceFile + ".html"); + } + } + } + + protected void handleBurnBootloader(final String target, final boolean parallel) { if(debugging) doStop(); @@ -2083,6 +2272,7 @@ public class Editor extends JFrame }}); } + public void highlightLine(int lnum) { if (lnum < 0) { textarea.select(0, 0); @@ -2169,7 +2359,7 @@ public class Editor extends JFrame public void error(RunnerException e) { - //System.out.println("ERORROOROROR 2"); + //System.out.println("file and line is " + e.file + " " + e.line); if (e.file >= 0) sketch.setCurrent(e.file); if (e.line >= 0) highlightLine(e.line); @@ -2205,7 +2395,7 @@ public class Editor extends JFrame * Returns the edit popup menu. */ class TextAreaPopup extends JPopupMenu { - String currentDir = System.getProperty("user.dir"); + //String currentDir = System.getProperty("user.dir"); String referenceFile = null; JMenuItem cutItem, copyItem; @@ -2251,13 +2441,14 @@ public class Editor extends JFrame this.addSeparator(); -// referenceItem = new JMenuItem("Find in Reference"); -// referenceItem.addActionListener(new ActionListener() { -// public void actionPerformed(ActionEvent e) { -// Base.showReference(referenceFile); -// } -// }); -// this.add(referenceItem); + referenceItem = new JMenuItem("Find in Reference"); + referenceItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + //Base.showReference(referenceFile + ".html"); + handleReference(); //textarea.getSelectedText()); + } + }); + this.add(referenceItem); } // if no text is selected, disable copy and cut menu items @@ -2266,14 +2457,14 @@ public class Editor extends JFrame cutItem.setEnabled(true); copyItem.setEnabled(true); - //referenceFile = PdeKeywords.getReference(textarea.getSelectedText()); - //if (referenceFile != null) { - //referenceItem.setEnabled(true); - //} + String sel = textarea.getSelectedText().trim(); + referenceFile = PdeKeywords.getReference(sel); + referenceItem.setEnabled(referenceFile != null); + } else { cutItem.setEnabled(false); copyItem.setEnabled(false); - //referenceItem.setEnabled(false); + referenceItem.setEnabled(false); } super.show(component, x, y); } diff --git a/app/EditorHeader.java b/app/EditorHeader.java index 2fc60c90f..e4ca49b4e 100644 --- a/app/EditorHeader.java +++ b/app/EditorHeader.java @@ -241,6 +241,7 @@ public class EditorHeader extends JComponent { public void rebuildMenu() { + //System.out.println("rebuilding"); if (menu != null) { menu.removeAll(); @@ -248,6 +249,9 @@ public class EditorHeader extends JComponent { menu = new JMenu(); popup = menu.getPopupMenu(); add(popup); + popup.setLightWeightPopupEnabled(true); + + /* popup.addPopupMenuListener(new PopupMenuListener() { public void popupMenuCanceled(PopupMenuEvent e) { // on redraw, the isVisible() will get checked. @@ -259,6 +263,7 @@ public class EditorHeader extends JComponent { public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } public void popupMenuWillBecomeVisible(PopupMenuEvent e) { } }); + */ } JMenuItem item; @@ -352,6 +357,43 @@ public class EditorHeader extends JComponent { } menu.add(unhide); + menu.addSeparator(); + + // KeyEvent.VK_LEFT and VK_RIGHT will make Windows beep + + int ctrlAlt = ActionEvent.ALT_MASK | + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + + //item = Editor.newJMenuItem("Previous Tab", '[', true); + item = new JMenuItem("Previous Tab"); + //int shortcut = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + KeyStroke ctrlAltLeft = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, ctrlAlt); + item.setAccelerator(ctrlAltLeft); + //int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + //KeyStroke tabby = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, modifiers); + + // this didn't want to work consistently + /* + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + editor.sketch.prevCode(); + } + }); + */ + menu.add(item); + + //item = Editor.newJMenuItem("Next Tab", ']', true); + item = new JMenuItem("Next Tab"); + KeyStroke ctrlAltRight = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, ctrlAlt); + item.setAccelerator(ctrlAltRight); + /* + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + editor.sketch.nextCode(); + } + }); + */ + menu.add(item); if (sketch != null) { menu.addSeparator(); diff --git a/app/EditorListener.java b/app/EditorListener.java index 8f6c41ad7..b8b9e791c 100644 --- a/app/EditorListener.java +++ b/app/EditorListener.java @@ -60,6 +60,10 @@ public class EditorListener { int selectionStart, selectionEnd; int position; + /** ctrl-alt on windows and linux, cmd-alt on mac os x */ + static final int CTRL_ALT = ActionEvent.ALT_MASK | + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + public EditorListener(Editor editor, JEditTextArea textarea) { this.editor = editor; @@ -105,6 +109,16 @@ public class EditorListener { //System.out.println(c + " " + code + " " + event); //System.out.println(); + if ((event.getModifiers() & CTRL_ALT) == CTRL_ALT) { + if (code == KeyEvent.VK_LEFT) { + editor.sketch.prevCode(); + return true; + } else if (code == KeyEvent.VK_RIGHT) { + editor.sketch.nextCode(); + return true; + } + } + if ((event.getModifiers() & KeyEvent.META_MASK) != 0) { //event.consume(); // does nothing return false; @@ -195,8 +209,8 @@ public class EditorListener { switch ((int) c) { - case 9: // expand tabs - if (tabsExpand) { + case 9: + if (tabsExpand) { // expand tabs textarea.setSelectedText(tabString); event.consume(); return true; @@ -286,12 +300,40 @@ public class EditorListener { origIndex += offset; // ARGH!#(* WINDOWS#@($* */ + // if the previous thing is a brace (whether prev line or + // up farther) then the correct indent is the number of spaces + // on that line + 'indent'. + // if the previous line is not a brace, then just use the + // identical indentation to the previous line + + // calculate the amount of indent on the previous line + // this will be used *only if the prev line is not an indent* int spaceCount = calcSpaceCount(origIndex, contents); - //int origCount = spaceCount; + + // If the last character was a left curly brace, then indent. + // For 0122, walk backwards a bit to make sure that the there + // isn't a curly brace several spaces (or lines) back. Also + // moved this before calculating extraCount, since it'll affect + // that as well. + int index2 = origIndex; + while ((index2 >= 0) && + Character.isWhitespace(contents[index2])) { + index2--; + } + if (index2 != -1) { + // still won't catch a case where prev stuff is a comment + if (contents[index2] == '{') { + // intermediate lines be damned, + // use the indent for this line instead + spaceCount = calcSpaceCount(index2, contents); + spaceCount += tabSize; + } + } + //System.out.println("spaceCount should be " + spaceCount); // now before inserting this many spaces, walk forward from - // the caret position, so that the number of spaces aren't - // just being duplicated again + // the caret position and count the number of spaces, + // so that the number of spaces aren't duplicated again int index = origIndex + 1; int extraCount = 0; while ((index < contents.length) && @@ -300,23 +342,58 @@ public class EditorListener { extraCount++; index++; } + int braceCount = 0; + while ((index < contents.length) && (contents[index] != '\n')) { + if (contents[index] == '}') { + braceCount++; + } + index++; + } // hitting return on a line with spaces *after* the caret - // can cause trouble. for simplicity's sake, just ignore this case. - //if (spaceCount < 0) spaceCount = origCount; + // can cause trouble. for 0099, was ignoring the case, but this is + // annoying, so in 0122 we're trying to fix that. + /* if (spaceCount - extraCount > 0) { spaceCount -= extraCount; } + */ + spaceCount -= extraCount; + //if (spaceCount < 0) spaceCount = 0; + //System.out.println("extraCount is " + extraCount); - // if the last character was a left curly brace, then indent - if (origIndex != -1) { - if (contents[origIndex] == '{') { - spaceCount += tabSize; - } - } + // now, check to see if the current line contains a } and if so, + // outdent again by indent + //if (braceCount > 0) { + //spaceCount -= 2; + //} + if (spaceCount < 0) { + // for rev 0122, actually delete extra space + //textarea.setSelectionStart(origIndex + 1); + textarea.setSelectionEnd(textarea.getSelectionEnd() - spaceCount); + textarea.setSelectedText("\n"); + } else { String insertion = "\n" + Editor.EMPTY.substring(0, spaceCount); textarea.setSelectedText(insertion); + } + + // not gonna bother handling more than one brace + if (braceCount > 0) { + int sel = textarea.getSelectionStart(); + // sel - tabSize will be -1 if start/end parens on the same line + // http://dev.processing.org/bugs/show_bug.cgi?id=484 + if (sel - tabSize >= 0) { + textarea.select(sel - tabSize, sel); + String s = Editor.EMPTY.substring(0, tabSize); + // if these are spaces that we can delete + if (textarea.getSelectedText().equals(s)) { + textarea.setSelectedText(""); + } else { + textarea.select(sel, sel); + } + } + } // mark this event as already handled event.consume(); @@ -424,6 +501,34 @@ public class EditorListener { } + /** Cmd-Shift or Ctrl-Shift depending on the platform */ + //static final int CMD_SHIFT = ActionEvent.SHIFT_MASK | + // Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + /** ctrl-alt on windows and linux, cmd-alt on mac os x */ + //static final int CTRL_ALT = ActionEvent.ALT_MASK | + // Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + + /* + public boolean keyTyped(KeyEvent event) { + char c = event.getKeyChar(); + int code = event.getKeyCode(); + + if ((event.getModifiers() & CMD_ALT) == CMD_ALT) { + if (code == KeyEvent.VK_LEFT) { + //if (c == '[') { + editor.sketch.prevCode(); + return true; + } else if (code == KeyEvent.VK_RIGHT) { + //} else if (c == ']') { + editor.sketch.nextCode(); + return true; + } + } + return false; + } + */ + + /** * Return the index for the first character on this line. */ diff --git a/app/FindReplace.java b/app/FindReplace.java index 43f6ce43a..1dccf214d 100644 --- a/app/FindReplace.java +++ b/app/FindReplace.java @@ -57,6 +57,7 @@ public class FindReplace extends JFrame implements ActionListener { JButton replaceButton; JButton replaceAllButton; + JButton replaceFindButton; JButton findButton; JCheckBox ignoreCaseBox; @@ -132,12 +133,14 @@ public class FindReplace extends JFrame implements ActionListener { // ordering is different on mac versus pc if (Base.isMacOS()) { - buttons.add(replaceButton = new JButton("Replace")); buttons.add(replaceAllButton = new JButton("Replace All")); + buttons.add(replaceButton = new JButton("Replace")); + buttons.add(replaceFindButton = new JButton("Replace & Find")); buttons.add(findButton = new JButton("Find")); } else { buttons.add(findButton = new JButton("Find")); + buttons.add(replaceFindButton = new JButton("Replace & Find")); buttons.add(replaceButton = new JButton("Replace")); buttons.add(replaceAllButton = new JButton("Replace All")); } @@ -169,10 +172,12 @@ public class FindReplace extends JFrame implements ActionListener { replaceButton.addActionListener(this); replaceAllButton.addActionListener(this); + replaceFindButton.addActionListener(this); findButton.addActionListener(this); // you mustn't replace what you haven't found, my son replaceButton.setEnabled(false); + replaceFindButton.setEnabled(false); // so that typing will go straight to this field //findField.requestFocus(); @@ -189,44 +194,28 @@ public class FindReplace extends JFrame implements ActionListener { setBounds((screen.width - wide) / 2, (screen.height - high) / 2, wide, high); - // add key listener to trap esc and ctrl/cmd-w - /* - KeyListener listener = new KeyAdapter() { - public void keyPressed(KeyEvent e) { - if (Base.isCloseWindowEvent(e)) hide(); - } - }; - findField.addKeyListener(listener); - replaceField.addKeyListener(listener); - addKeyListener(listener); - */ - ActionListener disposer = new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - //hide(); - handleClose(); - } - }; - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { handleClose(); } }); - Base.registerWindowCloseKeys(getRootPane(), disposer); + Base.registerWindowCloseKeys(getRootPane(), new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + //hide(); + handleClose(); + } + }); - /* // hack to to get first field to focus properly on osx - // though this still doesn't seem to work addWindowListener(new WindowAdapter() { public void windowActivated(WindowEvent e) { //System.out.println("activating"); - //boolean ok = findField.requestFocusInWindow(); + boolean ok = findField.requestFocusInWindow(); //System.out.println("got " + ok); - //findField.selectAll(); + findField.selectAll(); } }); - */ } @@ -256,6 +245,10 @@ public class FindReplace extends JFrame implements ActionListener { if (source == findButton) { find(true); + } else if (source == replaceFindButton) { + replace(); + find(true); + } else if (source == replaceButton) { replace(); @@ -276,6 +269,7 @@ public class FindReplace extends JFrame implements ActionListener { found = false; String search = findField.getText(); + //System.out.println("finding for " + search + " " + findString); // this will catch "find next" being called when no search yet if (search.length() == 0) return; @@ -299,12 +293,14 @@ public class FindReplace extends JFrame implements ActionListener { if (nextIndex == -1) { found = false; replaceButton.setEnabled(false); + replaceFindButton.setEnabled(false); //Toolkit.getDefaultToolkit().beep(); return; } } found = true; replaceButton.setEnabled(true); + replaceFindButton.setEnabled(true); editor.textarea.select(nextIndex, nextIndex + search.length()); } @@ -322,6 +318,7 @@ public class FindReplace extends JFrame implements ActionListener { if (sel.equals(replaceField.getText())) { found = false; replaceButton.setEnabled(false); + replaceFindButton.setEnabled(false); return; } @@ -332,6 +329,7 @@ public class FindReplace extends JFrame implements ActionListener { // don't allow a double replace replaceButton.setEnabled(false); + replaceFindButton.setEnabled(false); } diff --git a/app/Library.java b/app/Library.java index 24a7eac17..09b5d44a1 100755 --- a/app/Library.java +++ b/app/Library.java @@ -31,6 +31,8 @@ import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; +import processing.core.*; + /* * Provides information about and builds a library */ @@ -552,7 +554,7 @@ public class Library implements MessageConsumer{ continue; } - String pieces[] = Base.split(line, '\t'); + String pieces[] = PApplet.split(line, '\t'); if (pieces.length >= 2) { String keyword = pieces[0].trim(); diff --git a/app/Preferences.java b/app/Preferences.java index 671837b2d..29916afba 100644 --- a/app/Preferences.java +++ b/app/Preferences.java @@ -41,7 +41,12 @@ import javax.swing.filechooser.*; import javax.swing.text.*; import javax.swing.undo.*; -//import processing.core.PApplet; +import processing.core.PApplet; + + +// TODO change this to use the Java Preferences API +// http://www.onjava.com/pub/a/onjava/synd/2001/10/17/j2se.html +// http://www.particle.kth.se/~lindsey/JavaCourse/Book/Part1/Java/Chapter10/Preferences.html /** @@ -50,6 +55,10 @@ import javax.swing.undo.*; * This class no longer uses the Properties class, since * properties files are iso8859-1, which is highly likely to * be a problem when trying to save sketch folders and locations. + *

+ * This is very poorly put together, that the prefs panel and the + * actual prefs i/o is part of the same code. But there hasn't yet + * been a compelling reason to bother with the separation. */ public class Preferences { @@ -88,9 +97,12 @@ public class Preferences { * inside a static block. */ static public int BUTTON_HEIGHT = 24; + /* + // remove this for 0121, because quaqua takes care of it static { if (Base.isMacOS()) BUTTON_HEIGHT = 29; } + */ // value for the size bars, buttons, etc @@ -107,15 +119,18 @@ public class Preferences { // gui elements - JDialog dialog; + //JDialog dialog; + JFrame dialog; int wide, high; JTextField sketchbookLocationField; + JCheckBox exportSeparateBox; JCheckBox sketchPromptBox; JCheckBox sketchCleanBox; JCheckBox externalEditorBox; + JCheckBox memoryOverrideBox; + JTextField memoryField; JCheckBox checkUpdatesBox; - JTextField fontSizeField; @@ -145,7 +160,7 @@ public class Preferences { // check for platform-specific properties in the defaults String platformExtension = "." + - platforms[Base.platform]; + platforms[processing.core.PApplet.platform]; int extensionLength = platformExtension.length(); Enumeration e = table.keys(); //properties.propertyNames(); @@ -195,7 +210,8 @@ public class Preferences { // setup dialog for the prefs - dialog = new JDialog(editor, "Preferences", true); + //dialog = new JDialog(editor, "Preferences", true); + dialog = new JFrame("Preferences"); dialog.setResizable(false); Container pain = dialog.getContentPane(); @@ -249,6 +265,7 @@ public class Preferences { button = new JButton(PROMPT_BROWSE); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { + /* JFileChooser fc = new JFileChooser(); fc.setSelectedFile(new File(sketchbookLocationField.getText())); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); @@ -258,6 +275,13 @@ public class Preferences { File file = fc.getSelectedFile(); sketchbookLocationField.setText(file.getAbsolutePath()); } + */ + File dflt = new File(sketchbookLocationField.getText()); + File file = + Base.selectFolder("Select new sketchbook location", dflt, dialog); + if (file != null) { + sketchbookLocationField.setText(file.getAbsolutePath()); + } } }); pain.add(button); @@ -286,6 +310,8 @@ public class Preferences { box.add(label); fontSizeField = new JTextField(4); box.add(fontSizeField); + label = new JLabel(" (requires restart of Arduino)"); + box.add(label); pain.add(box); d = box.getPreferredSize(); box.setBounds(left, top, d.width, d.height); @@ -468,9 +494,9 @@ public class Preferences { String newSizeText = fontSizeField.getText(); try { int newSize = Integer.parseInt(newSizeText.trim()); - String pieces[] = Base.split(get("editor.font"), ','); + String pieces[] = PApplet.split(get("editor.font"), ','); pieces[2] = String.valueOf(newSize); - set("editor.font", Base.join(pieces, ',')); + set("editor.font", PApplet.join(pieces, ',')); } catch (Exception e) { System.err.println("ignoring invalid font size " + newSizeText); diff --git a/app/Runner.java b/app/Runner.java index 0ae4829e6..ba912f770 100644 --- a/app/Runner.java +++ b/app/Runner.java @@ -23,7 +23,7 @@ package processing.app; -//import processing.core.*; +import processing.core.*; import java.awt.*; import java.awt.event.*; @@ -40,7 +40,7 @@ import com.oroinc.text.regex.*; */ public class Runner implements MessageConsumer { - //PApplet applet; + PApplet applet; RunnerException exception; Window window; PrintStream leechErr; @@ -497,6 +497,7 @@ java.lang.NullPointerException } if (codeIndex != -1) { + //System.out.println("got line num " + lineNumber); // in case this was a tab that got embedded into the main .java lineNumber -= sketch.code[codeIndex].preprocOffset; diff --git a/app/Sketch.java b/app/Sketch.java index 19dab5b9f..2623baabf 100644 --- a/app/Sketch.java +++ b/app/Sketch.java @@ -24,7 +24,7 @@ package processing.app; import processing.app.preproc.*; -//import processing.core.*; +import processing.core.*; import java.awt.*; import java.io.*; @@ -73,6 +73,7 @@ public class Sketch { static final String flavorExtensionsShown[] = new String[] { "", ".cpp", ".c", ".h" }; public SketchCode current; + int currentIndex; int codeCount; SketchCode code[]; @@ -225,7 +226,11 @@ public class Sketch { } } - // remove any entries that didn't load properly + // some of the hidden files may be bad too, so use hiddenCounter + // added for rev 0121, fixes bug found by axel + hiddenCount = hiddenCounter; + + // remove any entries that didn't load properly from codeCount int index = 0; while (index < codeCount) { if ((code[index] == null) || @@ -330,7 +335,7 @@ public class Sketch { // ask for new name of file (internal to window) // TODO maybe just popup a text area? renamingCode = true; - String prompt = (current == code[0]) ? + String prompt = (currentIndex == 0) ? "New name for sketch:" : "New name for file:"; String oldName = current.name + flavorExtensionsShown[current.flavor]; editor.status.edit(prompt, oldName); @@ -436,7 +441,7 @@ public class Sketch { } if (renamingCode) { - if (current == code[0]) { + if (currentIndex == 0) { // get the new folder name/location File newFolder = new File(folder.getParentFile(), newName); if (newFolder.exists()) { @@ -494,43 +499,17 @@ public class Sketch { // having saved everything and renamed the folder and the main .pde, // use the editor to re-open the sketch to re-init state // (unfortunately this will kill positions for carets etc) - editor.handleOpenUnchecked(mainFilename); - - /* - // backtrack and don't rename the sketch folder - success = newFolder.renameTo(folder); - if (!success) { - String msg = - "Started renaming sketch and then ran into\n" + - "nasty trouble. Try to salvage with Copy & Paste\n" + - "or attempt a \"Save As\" to see if that works."; - Base.showWarning("Serious Error", msg, null); - } - return; - } - */ - - /* - // set the sketch name... used by the pde and whatnot. - // the name is only set in the sketch constructor, - // so it's important here - name = newName; - - code[0].name = newName; - code[0].file = mainFile; - code[0].program = editor.getText(); - code[0].save(); - - folder = newFolder; + editor.handleOpenUnchecked(mainFilename, + currentIndex, + editor.textarea.getSelectionStart(), + editor.textarea.getSelectionEnd(), + editor.textarea.getScrollPosition()); // get the changes into the sketchbook menu + // (re-enabled in 0115 to fix bug #332) editor.sketchbook.rebuildMenus(); - // reload the sketch - load(); - */ - - } else { + } else { // else if something besides code[0] if (!current.file.renameTo(newFile)) { Base.showWarning("Error", "Could not rename \"" + current.file.getName() + @@ -593,7 +572,7 @@ public class Sketch { // confirm deletion with user, yes/no Object[] options = { "OK", "Cancel" }; - String prompt = (current == code[0]) ? + String prompt = (currentIndex == 0) ? "Are you sure you want to delete this sketch?" : "Are you sure you want to delete \"" + current.name + flavorExtensionsShown[current.flavor] + "\"?"; @@ -606,7 +585,7 @@ public class Sketch { options, options[0]); if (result == JOptionPane.YES_OPTION) { - if (current == code[0]) { + if (currentIndex == 0) { // need to unset all the modified flags, otherwise tries // to do a save on the handleNew() @@ -672,7 +651,7 @@ public class Sketch { // don't allow hide of the main code // TODO maybe gray out the menu on setCurrent(0) - if (current == code[0]) { + if (currentIndex == 0) { Base.showMessage("Can't do that", "You cannot hide the main " + ".pde file from a sketch\n"); @@ -933,57 +912,14 @@ public class Sketch { File newFile = new File(newFolder, newName + ".pde"); code[0].saveAs(newFile); - editor.handleOpenUnchecked(newFile.getPath()); + editor.handleOpenUnchecked(newFile.getPath(), + currentIndex, + editor.textarea.getSelectionStart(), + editor.textarea.getSelectionEnd(), + editor.textarea.getScrollPosition()); - /* - // copy the entire contents of the sketch folder - Base.copyDir(folder, newFolder); - - // change the references to the dir location in SketchCode files - for (int i = 0; i < codeCount; i++) { - code[i].file = new File(newFolder, code[i].file.getName()); - } - for (int i = 0; i < hiddenCount; i++) { - hidden[i].file = new File(newFolder, hidden[i].file.getName()); - } - - // remove the old sketch file from the new dir - code[0].file.delete(); - // name for the new main .pde file - code[0].file = new File(newFolder, newName + ".pde"); - code[0].name = newName; - // write the contents to the renamed file - // (this may be resaved if the code is modified) - code[0].modified = true; - //code[0].save(); - //System.out.println("modified is " + modified); - - // change the other paths - String oldName = name; - name = newName; - File oldFolder = folder; - folder = newFolder; - dataFolder = new File(folder, "data"); - codeFolder = new File(folder, "code"); - - // remove the 'applet', 'application', 'library' folders - // from the copied version. - // otherwise their .class and .jar files can cause conflicts. - Base.removeDir(new File(folder, "applet")); - Base.removeDir(new File(folder, "application")); - //Base.removeDir(new File(folder, "library")); - - // do a "save" - // this will take care of the unsaved changes in each of the tabs - save(); - - // get the changes into the sketchbook menu - //sketchbook.rebuildMenu(); - // done inside Editor instead - - // update the tabs for the name change - editor.header.repaint(); - */ + // Name changed, rebuild the sketch menus + editor.sketchbook.rebuildMenusAsync(); // let Editor know that the save was successful return true; @@ -1168,8 +1104,8 @@ public class Sketch { * */ public void setCurrent(int which) { - if (current == code[which]) { - //System.out.println("already current, ignoring"); + // if current is null, then this is the first setCurrent(0) + if ((currentIndex == which) && (current != null)) { return; } @@ -1182,6 +1118,7 @@ public class Sketch { } current = code[which]; + currentIndex = which; editor.setCode(current); //editor.setDocument(current.document, // current.selectionStart, current.selectionStop, @@ -1286,6 +1223,9 @@ public class Sketch { //handleOpen(sketch); //history.lastRecorded = historySaved; + // set current to null so that the tab gets updated + // http://dev.processing.org/bugs/show_bug.cgi?id=515 + current = null; // nuke previous files and settings, just get things loaded load(); } @@ -1746,7 +1686,12 @@ public class Sketch { // make sure the user didn't hide the sketch folder ensureExistence(); - current.program = editor.getText(); + // fix for issue posted on the board. make sure that the code + // is reloaded when exporting and an external editor is being used. + if (Preferences.getBoolean("editor.external")) { + // nuke previous files and settings + load(); + } zipFileContents = new Hashtable(); @@ -1773,6 +1718,7 @@ public class Sketch { /* int wide = PApplet.DEFAULT_WIDTH; int high = PApplet.DEFAULT_HEIGHT; + String renderer = ""; PatternMatcher matcher = new Perl5Matcher(); PatternCompiler compiler = new Perl5Compiler(); @@ -1785,20 +1731,26 @@ public class Sketch { // modified for 83 to match size(XXX, ddd so that it'll // properly handle size(200, 200) and size(200, 200, P3D) String sizing = - "[\\s\\;]size\\s*\\(\\s*(\\S+)\\s*,\\s*(\\d+)"; + // match the renderer string as well + "[\\s\\;]size\\s*\\(\\s*(\\S+)\\s*,\\s*(\\d+),?\\s*([^\\)]*)\\s*\\)"; + // match just the width and height + //"[\\s\\;]size\\s*\\(\\s*(\\S+)\\s*,\\s*(\\d+)(.*)\\)"; Pattern pattern = compiler.compile(sizing); // adds a space at the beginning, in case size() is the very // first thing in the program (very common), since the regexp // needs to check for things in front of it. PatternMatcherInput input = - new PatternMatcherInput(" " + code[0].program); + new PatternMatcherInput(" " + scrubComments(code[0].program)); if (matcher.contains(input, pattern)) { MatchResult result = matcher.getMatch(); + try { wide = Integer.parseInt(result.group(1).toString()); high = Integer.parseInt(result.group(2).toString()); + renderer = result.group(3).toString(); //.trim(); + } catch (NumberFormatException e) { // found a reference to size, but it didn't // seem to contain numbers @@ -1865,45 +1817,6 @@ public class Sketch { if (is == null) { is = Base.getStream("applet.html"); } - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - - String line = null; - while ((line = reader.readLine()) != null) { - if (line.indexOf("@@") != -1) { - StringBuffer sb = new StringBuffer(line); - int index = 0; - while ((index = sb.indexOf("@@sketch@@")) != -1) { - sb.replace(index, index + "@@sketch@@".length(), - name); - } - while ((index = sb.indexOf("@@source@@")) != -1) { - sb.replace(index, index + "@@source@@".length(), - sources.toString()); - } - while ((index = sb.indexOf("@@archive@@")) != -1) { - sb.replace(index, index + "@@archive@@".length(), - name + ".jar"); - } - while ((index = sb.indexOf("@@width@@")) != -1) { - sb.replace(index, index + "@@width@@".length(), - String.valueOf(wide)); - } - while ((index = sb.indexOf("@@height@@")) != -1) { - sb.replace(index, index + "@@height@@".length(), - String.valueOf(high)); - } - while ((index = sb.indexOf("@@description@@")) != -1) { - sb.replace(index, index + "@@description@@".length(), - description); - } - line = sb.toString(); - } - ps.println(line); - } - - reader.close(); - ps.flush(); - ps.close(); // copy the loading gif to the applet String LOADING_IMAGE = "loading.gif"; @@ -1929,13 +1842,13 @@ public class Sketch { new FileOutputStream(new File(appletFolder, name + ".jar")); ZipOutputStream zos = new ZipOutputStream(zipOutputFile); ZipEntry entry; + archives.append(name + ".jar"); // add the manifest file addManifest(zos); // add the contents of the code folder to the jar - // unpacks all jar files - //File codeFolder = new File(folder, "code"); + // unpacks all jar files, unless multi jar files selected in prefs if (codeFolder.exists()) { String includes = Compiler.contentsToClassPath(codeFolder); packClassPathIntoZipFile(includes, zos); @@ -1980,7 +1893,18 @@ public class Sketch { } else if (exportFile.getName().toLowerCase().endsWith(".zip") || exportFile.getName().toLowerCase().endsWith(".jar")) { + if (separateJar) { + String exportFilename = exportFile.getName(); + Base.copyFile(exportFile, new File(appletFolder, exportFilename)); + if (renderer.equals("OPENGL") && + exportFilename.indexOf("natives") != -1) { + // don't add these to the archives list + } else { + archives.append("," + exportFilename); + } + } else { packClassPathIntoZipFile(exportFile.getAbsolutePath(), zos); + } } else { // just copy the file over.. prolly a .dll or something Base.copyFile(exportFile, @@ -1991,6 +1915,7 @@ public class Sketch { */ /* String bagelJar = "lib/core.jar"; packClassPathIntoZipFile(bagelJar, zos); + } // files to include from data directory // TODO this needs to be recursive @@ -2049,6 +1974,53 @@ public class Sketch { } + static public String scrubComments(String what) { + char p[] = what.toCharArray(); + + int index = 0; + while (index < p.length) { + // for any double slash comments, ignore until the end of the line + if ((p[index] == '/') && + (index < p.length - 1) && + (p[index+1] == '/')) { + p[index++] = ' '; + p[index++] = ' '; + while ((index < p.length) && + (p[index] != '\n')) { + p[index++] = ' '; + } + + // check to see if this is the start of a new multiline comment. + // if it is, then make sure it's actually terminated somewhere. + } else if ((p[index] == '/') && + (index < p.length - 1) && + (p[index+1] == '*')) { + p[index++] = ' '; + p[index++] = ' '; + boolean endOfRainbow = false; + while (index < p.length - 1) { + if ((p[index] == '*') && (p[index+1] == '/')) { + p[index++] = ' '; + p[index++] = ' '; + endOfRainbow = true; + break; + + } else { + index++; + } + } + if (!endOfRainbow) { + throw new RuntimeException("Missing the */ from the end of a " + + "/* comment */"); + } + } else { // any old character, move along + index++; + } + } + return new String(p); + } + + /** * Export to application. *

@@ -2135,7 +2107,6 @@ public class Sketch {
 
     for (int i = 0; i < pieces.length; i++) {
       if (pieces[i].length() == 0) continue;
-      //System.out.println("checking piece " + pieces[i]);
 
       // is it a jar file or directory?
       if (pieces[i].toLowerCase().endsWith(".jar") ||
@@ -2299,4 +2270,16 @@ public class Sketch {
   public String getMainFilePath() {
     return code[0].file.getAbsolutePath();
   }
+
+
+  public void prevCode() {
+    int prev = currentIndex - 1;
+    if (prev < 0) prev = codeCount-1;
+    setCurrent(prev);
+  }
+
+
+  public void nextCode() {
+    setCurrent((currentIndex + 1) % codeCount);
+  }
 }
diff --git a/app/Sketchbook.java b/app/Sketchbook.java
index c79096258..322ea9e01 100644
--- a/app/Sketchbook.java
+++ b/app/Sketchbook.java
@@ -130,6 +130,7 @@ public class Sketchbook {
 
       //System.out.println("resetting sketchbook path");
       File sketchbookFolder = Base.getDefaultSketchbookFolder();
+      //System.out.println("default is " + sketchbookFolder);
       Preferences.set("sketchbook.path",
                       sketchbookFolder.getAbsolutePath());
 
@@ -170,11 +171,8 @@ public class Sketchbook {
     if (noPrompt) prompt = false;
 
     if (prompt) {
-    //if (!startup) {
       // prompt for the filename and location for the new sketch
-
-      FileDialog fd = new FileDialog(editor, //new Frame(),
-                                     //"Create new sketch named",
+      FileDialog fd = new FileDialog(editor,
                                      "Create sketch folder named:",
                                      FileDialog.SAVE);
       fd.setDirectory(getSketchbookPath());
@@ -228,7 +226,7 @@ public class Sketchbook {
     }
 
     // make a note of a newly added sketch in the sketchbook menu
-    rebuildMenus();
+    rebuildMenusAsync();
 
     // now open it up
     //handleOpen(newbieName, newbieFile, newbieDir);
@@ -257,10 +255,25 @@ public class Sketchbook {
 
 
   /**
-   * Java classes are pretty limited about what you can use
-   * for their naming. This helper function replaces everything
-   * but A-Z, a-z, and 0-9 with underscores. Also disallows
-   * starting the sketch name with a digit.
+   * Return true if the name is valid for a Processing sketch.
+   */
+  static public boolean isSanitary(String name) {
+    return sanitizedName(name).equals(name);
+  }
+
+
+  /**
+   * Produce a sanitized name that fits our standards for likely to work.
+   * 

+ * Java classes have a wider range of names that are technically allowed + * (supposedly any Unicode name) than what we support. The reason for + * going more narrow is to avoid situations with text encodings and + * converting during the process of moving files between operating + * systems, i.e. uploading from a Windows machine to a Linux server, + * or reading a FAT32 partition in OS X and using a thumb drive. + *

+ * This helper function replaces everything but A-Z, a-z, and 0-9 with + * underscores. Also disallows starting the sketch name with a digit. */ static public String sanitizedName(String origName) { char c[] = origName.toCharArray(); @@ -280,7 +293,12 @@ public class Sketchbook { buffer.append('_'); } } - // let's not be ridiculous about the length of filenames + // let's not be ridiculous about the length of filenames. + // in fact, Mac OS 9 can handle 255 chars, though it can't really + // deal with filenames longer than 31 chars in the Finder. + // but limiting to that for sketches would mean setting the + // upper-bound on the character limit here to 25 characters + // (to handle the base name + ".class") if (buffer.length() > 63) { buffer.setLength(63); } @@ -290,12 +308,12 @@ public class Sketchbook { public String handleOpen() { // swing's file choosers are ass ugly, so we use the - // native (awt peered) dialogs instead + // native (awt peered) dialogs where possible FileDialog fd = new FileDialog(editor, //new Frame(), "Open a Processing sketch...", FileDialog.LOAD); //fd.setDirectory(Preferences.get("sketchbook.path")); - fd.setDirectory(getSketchbookPath()); + //fd.setDirectory(getSketchbookPath()); // only show .pde files as eligible bachelors // TODO this doesn't seem to ever be used. AWESOME. @@ -324,6 +342,23 @@ public class Sketchbook { } + /** + * Asynchronous version of menu rebuild to be used on 'new' and 'save', + * to prevent the interface from locking up until the menus are done. + */ + public void rebuildMenusAsync() { + // disabling the async option for actual release, this hasn't been tested + /* + SwingUtilities.invokeLater(new Runnable() { + public void run() { + rebuildMenus(); + } + }); + */ + rebuildMenus(); + } + + /** * Rebuild the menu full of sketches based on the * contents of the sketchbook. @@ -333,6 +368,7 @@ public class Sketchbook { * the menu will disappear from its original location. */ public void rebuildMenus() { + //EditorConsole.systemOut.println("rebuilding menus"); try { // rebuild file/open and the toolbar popup menus buildMenu(openMenu); @@ -359,6 +395,7 @@ public class Sketchbook { "sketchbook menu. Things might get a little\n" + "kooky around here.", e); } + //EditorConsole.systemOut.println("done rebuilding menus"); } @@ -444,25 +481,8 @@ public class Sketchbook { if (list == null) return false; // alphabetize list, since it's not always alpha order - // use cheapie bubble-style sort which should be fine - // since not a tone of files, and things will mostly be sorted - // or may be completely sorted already by the os - for (int i = 0; i < list.length; i++) { - int who = i; - for (int j = i+1; j < list.length; j++) { - if (list[j].compareToIgnoreCase(list[who]) < 0) { - who = j; // this guy is earlier in the alphabet - } - } - if (who != i) { // swap with someone if changes made - String temp = list[who]; - list[who] = list[i]; - list[i] = temp; - } - } - - //SketchbookMenuListener listener = - //new SketchbookMenuListener(folder.getAbsolutePath()); + // replaced hella slow bubble sort with this feller for 0093 + Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -477,18 +497,23 @@ public class Sketchbook { list[i].equals("CVS")) continue; File subfolder = new File(folder, list[i]); - File lib = new File(subfolder, "library"); + if (!subfolder.isDirectory()) continue; + File entry = new File(subfolder, list[i] + ".pde"); // if a .pde file of the same prefix as the folder exists.. if (entry.exists()) { - String sanityCheck = sanitizedName(list[i]); - if (!sanityCheck.equals(list[i])) { + //String sanityCheck = sanitizedName(list[i]); + //if (!sanityCheck.equals(list[i])) { + if (!Sketchbook.isSanitary(list[i])) { if (!builtOnce) { - String mess = + String complaining = "The sketch \"" + list[i] + "\" cannot be used.\n" + - "Sketch names must contain only basic letters and numbers.\n" + - "(ascii only and no spaces, and it cannot start with a number)"; - Base.showMessage("Ignoring bad sketch name", mess); + "Sketch names must contain only basic letters and numbers\n" + + "(ASCII-only with no spaces, " + + "and it cannot start with a number).\n" + + "To get rid of this message, remove the sketch from\n" + + entry.getAbsolutePath(); + Base.showMessage("Ignoring sketch with bad name", complaining); } continue; } @@ -499,7 +524,8 @@ public class Sketchbook { menu.add(item); ifound = true; - } else { // might contain other dirs, get recursive + } else { + // not a sketch folder, but maybe a subfolder containing sketches JMenu submenu = new JMenu(list[i]); // needs to be separate var // otherwise would set ifound to false @@ -523,22 +549,8 @@ public class Sketchbook { if (list == null) return false; // alphabetize list, since it's not always alpha order - // use cheapie bubble-style sort which should be fine - // since not a tone of files, and things will mostly be sorted - // or may be completely sorted already by the os - for (int i = 0; i < list.length; i++) { - int who = i; - for (int j = i+1; j < list.length; j++) { - if (list[j].compareToIgnoreCase(list[who]) < 0) { - who = j; // this guy is earlier in the alphabet - } - } - if (who != i) { // swap with someone if changes made - String temp = list[who]; - list[who] = list[i]; - list[i] = temp; - } - } + // replaced hella slow bubble sort with this feller for 0093 + Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -591,6 +603,8 @@ public class Sketchbook { String packages[] = Compiler.packageListFromClassPath(libraryClassPath); for (int k = 0; k < packages.length; k++) { + //System.out.println(packages[k] + " -> " + exported); + //String already = (String) importToLibraryTable.get(packages[k]); importToLibraryTable.put(packages[k], exported); } */ @@ -600,11 +614,10 @@ public class Sketchbook { menu.add(item); ifound = true; - } else { // might contain other dirs, get recursive + } else { // not a library, but is still a folder, so recurse JMenu submenu = new JMenu(list[i]); - // needs to be separate var - // otherwise would set ifound to false - boolean found = addLibraries(submenu, subfolder); //, false); + // needs to be separate var, otherwise would set ifound to false + boolean found = addLibraries(submenu, subfolder); if (found) { menu.add(submenu); ifound = true; diff --git a/app/preproc/PdePreprocessor.java b/app/preproc/PdePreprocessor.java index 5690e3298..d20f17be1 100755 --- a/app/preproc/PdePreprocessor.java +++ b/app/preproc/PdePreprocessor.java @@ -30,7 +30,7 @@ package processing.app.preproc; import processing.app.*; -//import processing.core.*; +import processing.core.*; import java.io.*; @@ -99,6 +99,7 @@ public class PdePreprocessor { /** * preprocesses a pde file and write out a java file + * @param pretty true if should also space out/indent lines * @return the classname of the exported Java */ //public String write(String program, String buildPath, String name, @@ -109,10 +110,19 @@ public class PdePreprocessor { throws java.lang.Exception { // if the program ends with no CR or LF an OutOfMemoryError will happen. // not gonna track down the bug now, so here's a hack for it: - if ((program.length() > 0) && - program.charAt(program.length()-1) != '\n') { + // bug filed at http://dev.processing.org/bugs/show_bug.cgi?id=5 + //if ((program.length() > 0) && + //program.charAt(program.length()-1) != '\n') { program += "\n"; - } + //} + + // if the program ends with an unterminated multiline comment, + // an OutOfMemoryError or NullPointerException will happen. + // again, not gonna bother tracking this down, but here's a hack. + // http://dev.processing.org/bugs/show_bug.cgi?id=16 + Sketch.scrubComments(program); + // this returns the scrubbed version, but more important for this + // function, it'll check to see if there are errors with the comments. if (Preferences.getBoolean("preproc.substitute_unicode")) { // check for non-ascii chars (these will be/must be in unicode format) @@ -193,26 +203,6 @@ public class PdePreprocessor { extraImports = new String[imports.size()]; imports.copyInto(extraImports); - // if using opengl, add it to the special imports - /* - if (Preferences.get("renderer").equals("opengl")) { - extraImports = new String[imports.size() + 1]; - imports.copyInto(extraImports); - extraImports[extraImports.length - 1] = "processing.opengl.*"; - } - */ - - /* - if (codeFolderPackages != null) { - extraImports = new String[importsCount + codeFolderPackages.length]; - imports.copyInto(extraImports); - for (int i = 0; i < codeFolderPackages.length; i++) { - extraImports[importsCount + i] = codeFolderPackages[i] + ".*"; - } - codeFolderImports = null; - } - */ - if (codeFolderPackages != null) { codeFolderImports = new String[codeFolderPackages.length]; for (int i = 0; i < codeFolderPackages.length; i++) { diff --git a/app/syntax/InputHandler.java b/app/syntax/InputHandler.java index 3c2186792..6963d2dad 100644 --- a/app/syntax/InputHandler.java +++ b/app/syntax/InputHandler.java @@ -136,9 +136,10 @@ public abstract class InputHandler extends KeyAdapter { String name = (String)en.nextElement(); ActionListener _listener = getAction(name); - if(_listener == listener) + if(_listener == listener) { return name; } + } return null; } @@ -180,7 +181,6 @@ public abstract class InputHandler extends KeyAdapter /** * Grabs the next key typed event and invokes the specified * action with the key as a the action command. - * @param action The action */ public void grabNextKeyStroke(ActionListener listener) { @@ -743,19 +743,32 @@ public abstract class InputHandler extends KeyAdapter { JEditTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); + if(caret == textArea.getDocumentLength()) { + if (textArea.getSelectionStart() != + textArea.getSelectionEnd()) { + // just move to the end of the selection + textArea.select(caret, caret); + } else { + // beep at the user for being annoying textArea.getToolkit().beep(); - return; } - if(select) - textArea.select(textArea.getMarkPosition(), - caret + 1); - else + } else if (select) { + textArea.select(textArea.getMarkPosition(), caret+1); + + } else { + int start = textArea.getSelectionStart(); + int end = textArea.getSelectionEnd(); + if (start != end) { + textArea.select(end, end); + } else { textArea.setCaretPosition(caret + 1); } } + } + } public static class next_line implements ActionListener { @@ -774,7 +787,13 @@ public abstract class InputHandler extends KeyAdapter if(line == textArea.getLineCount() - 1) { - textArea.getToolkit().beep(); + //textArea.getToolkit().beep(); + int doc = textArea.getDocumentLength(); + if (select) { + textArea.select(textArea.getMarkPosition(), doc); + } else { + textArea.setCaretPosition(doc); + } return; } @@ -901,13 +920,19 @@ public abstract class InputHandler extends KeyAdapter return; } - if(select) - textArea.select(textArea.getMarkPosition(), - caret - 1); - else + if (select) { + textArea.select(textArea.getMarkPosition(), caret-1); + } else { + int start = textArea.getSelectionStart(); + int end = textArea.getSelectionEnd(); + if (start != end) { + textArea.select(start, start); + } else { textArea.setCaretPosition(caret - 1); } } + } + } public static class prev_line implements ActionListener { @@ -926,7 +951,14 @@ public abstract class InputHandler extends KeyAdapter if(line == 0) { - textArea.getToolkit().beep(); + if (select) { + if (textArea.getSelectionStart() != 0) { + textArea.select(textArea.getMarkPosition(), 0); + } + } else { + textArea.setCaretPosition(0); + } + //textArea.getToolkit().beep(); return; } diff --git a/app/syntax/JEditTextArea.java b/app/syntax/JEditTextArea.java index ba367d360..5b45b4e40 100644 --- a/app/syntax/JEditTextArea.java +++ b/app/syntax/JEditTextArea.java @@ -888,22 +888,20 @@ public class JEditTextArea extends JComponent */ public void setText(String text) { - try - { + try { document.beginCompoundEdit(); document.remove(0,document.getLength()); document.insertString(0,text,null); - } - catch(BadLocationException bl) - { + + } catch (BadLocationException bl) { bl.printStackTrace(); - } - finally - { + + } finally { document.endCompoundEdit(); } } + /** * Returns the specified substring of the document. * @param start The start offset @@ -1502,7 +1500,7 @@ public class JEditTextArea extends JComponent /** * Sets if the selection should be rectangular. - * @param overwrite True if the selection should be rectangular, + * @param rectSelect True if the selection should be rectangular, * false otherwise. */ public final void setSelectionRectangular(boolean rectSelect) @@ -1644,6 +1642,7 @@ public class JEditTextArea extends JComponent switch(evt.getID()) { case KeyEvent.KEY_TYPED: + //if ((editorListener != null) && !editorListener.keyTyped(evt)) { inputHandler.keyTyped(evt); break; case KeyEvent.KEY_PRESSED: @@ -2145,21 +2144,52 @@ public class JEditTextArea extends JComponent bl.printStackTrace(); } - // Ok, it's not a bracket... select the word - String lineText = getLineText(line); - char ch = lineText.charAt(Math.max(0,offset - 1)); - String noWordSep = (String)document.getProperty("noWordSep"); if(noWordSep == null) noWordSep = ""; - // If the user clicked on a non-letter char, - // we select the surrounding non-letters - boolean selectNoLetter = (!Character - .isLetterOrDigit(ch) - && noWordSep.indexOf(ch) == -1); + // Ok, it's not a bracket... select the word + String lineText = getLineText(line); int wordStart = 0; + int wordEnd = lineText.length(); + + char ch = lineText.charAt(Math.max(0,offset - 1)); + + // special case for whitespace (fry 0122, bug #348) + // this is really nasty.. turns out that double-clicking any non-letter + // or digit char gets lumped together.. sooo, this quickly gets messy, + // because really it needs to check whether the chars are of the same + // type.. so a double space or double - might be grouped together, + // but what about a +=1? do + and - get grouped but not the 1? blech, + // coming back to this later. it's not a difficult fix, just a + // time-consuming one to track down all the proper cases. + /* + if (ch == ' ') { + //System.out.println("yeehaa"); + + for(int i = offset - 1; i >= 0; i--) { + if (lineText.charAt(i) == ' ') { + wordStart = i; + } else { + break; + } + } + for(int i = offset; i < lineText.length(); i++) { + if (lineText.charAt(i) == ' ') { + wordEnd = i + 1; + } else { + break; + } + } + + } else { + */ + + // If the user clicked on a non-letter char, + // we select the surrounding non-letters + boolean selectNoLetter = (!Character.isLetterOrDigit(ch) + && noWordSep.indexOf(ch) == -1); for(int i = offset - 1; i >= 0; i--) { ch = lineText.charAt(i); @@ -2170,18 +2200,15 @@ public class JEditTextArea extends JComponent } } - int wordEnd = lineText.length(); - for(int i = offset; i < lineText.length(); i++) - { + for(int i = offset; i < lineText.length(); i++) { ch = lineText.charAt(i); - if(selectNoLetter ^ (!Character - .isLetterOrDigit(ch) && - noWordSep.indexOf(ch) == -1)) - { + if(selectNoLetter ^ (!Character.isLetterOrDigit(ch) && + noWordSep.indexOf(ch) == -1)) { wordEnd = i; break; } } + //} int lineStart = getLineStartOffset(line); select(lineStart + wordStart,lineStart + wordEnd); diff --git a/app/syntax/KeywordMap.java b/app/syntax/KeywordMap.java index c19b4a777..d0d834adf 100644 --- a/app/syntax/KeywordMap.java +++ b/app/syntax/KeywordMap.java @@ -76,7 +76,7 @@ public class KeywordMap /** * Adds a key-value mapping. * @param keyword The key - * @Param id The value + * @param id The value */ public void add(String keyword, byte id) { diff --git a/app/syntax/PdeKeywords.java b/app/syntax/PdeKeywords.java index cba279213..8dec16b6c 100644 --- a/app/syntax/PdeKeywords.java +++ b/app/syntax/PdeKeywords.java @@ -76,7 +76,7 @@ public class PdeKeywords extends CTokenMarker { } } - String pieces[] = Base.split(line, '\t'); + String pieces[] = processing.core.PApplet.split(line, '\t'); if (pieces.length >= 2) { //int tab = line.indexOf('\t'); // any line with no tab is ignored diff --git a/app/syntax/PdeTextAreaDefaults.java b/app/syntax/PdeTextAreaDefaults.java index e9caca886..e85dad955 100644 --- a/app/syntax/PdeTextAreaDefaults.java +++ b/app/syntax/PdeTextAreaDefaults.java @@ -2,9 +2,9 @@ /* PdeTextAreaDefaults - grabs font/color settings for the editor - Part of the Processing project - http://Proce55ing.net + Part of the Processing project - http://processing.org - Except where noted, code is written by Ben Fry + Copyright (c) 2004-06 Ben Fry and Casey Reas Copyright (c) 2001-03 Massachusetts Institute of Technology This program is free software; you can redistribute it and/or modify @@ -32,41 +32,65 @@ public class PdeTextAreaDefaults extends TextAreaDefaults { public PdeTextAreaDefaults() { inputHandler = new DefaultInputHandler(); - inputHandler.addDefaultKeyBindings(); + //inputHandler.addDefaultKeyBindings(); // 0122 // use option on mac for things that are ctrl on windows/linux String mod = Base.isMacOS() ? "A" : "C"; - inputHandler.addKeyBinding("S+BACK_SPACE", InputHandler.BACKSPACE); - inputHandler.addKeyBinding("S+DELETE", InputHandler.DELETE); + // right now, ctrl-up/down is select up/down, but mod should be + // used instead, because the mac expects it to be option(alt) inputHandler.addKeyBinding("BACK_SPACE", InputHandler.BACKSPACE); - inputHandler.addKeyBinding("C+BACK_SPACE", InputHandler.BACKSPACE_WORD); inputHandler.addKeyBinding("DELETE", InputHandler.DELETE); - inputHandler.addKeyBinding("C+DELETE", InputHandler.DELETE_WORD); - inputHandler.addKeyBinding("ENTER", InputHandler.INSERT_BREAK); - inputHandler.addKeyBinding("TAB", InputHandler.INSERT_TAB); + //inputHandler.addKeyBinding("S+BACK_SPACE", InputHandler.BACKSPACE); + // for 0122, shift-backspace is delete + inputHandler.addKeyBinding("S+BACK_SPACE", InputHandler.DELETE); + inputHandler.addKeyBinding("S+DELETE", InputHandler.DELETE); + + // the following two were changing for 0122 for better mac/pc compatability + inputHandler.addKeyBinding(mod+"+BACK_SPACE", InputHandler.BACKSPACE_WORD); + inputHandler.addKeyBinding(mod+"+DELETE", InputHandler.DELETE_WORD); + + // handled by listener, don't bother here + //inputHandler.addKeyBinding("ENTER", InputHandler.INSERT_BREAK); + //inputHandler.addKeyBinding("TAB", InputHandler.INSERT_TAB); inputHandler.addKeyBinding("INSERT", InputHandler.OVERWRITE); - inputHandler.addKeyBinding("C+\\", InputHandler.TOGGLE_RECT); + // disabling for 0122, not sure what this does + //inputHandler.addKeyBinding("C+\\", InputHandler.TOGGLE_RECT); - // beginning and ending of the current line + // for 0122, these have been changed for better compatability + // HOME and END now mean the beginning/end of the document + if (Base.isMacOS()) { + inputHandler.addKeyBinding("HOME", InputHandler.DOCUMENT_HOME); + inputHandler.addKeyBinding("END", InputHandler.DOCUMENT_END); + inputHandler.addKeyBinding("S+HOME", InputHandler.SELECT_DOC_HOME); + inputHandler.addKeyBinding("S+END", InputHandler.SELECT_DOC_END); + } else { + // for 0123 added the proper windows defaults inputHandler.addKeyBinding("HOME", InputHandler.HOME); inputHandler.addKeyBinding("END", InputHandler.END); + inputHandler.addKeyBinding("S+HOME", InputHandler.SELECT_HOME); + inputHandler.addKeyBinding("S+END", InputHandler.SELECT_END); + inputHandler.addKeyBinding("C+HOME", InputHandler.DOCUMENT_HOME); + inputHandler.addKeyBinding("C+END", InputHandler.DOCUMENT_END); + inputHandler.addKeyBinding("CS+HOME", InputHandler.SELECT_DOC_HOME); + inputHandler.addKeyBinding("CS+END", InputHandler.SELECT_DOC_END); + } if (Base.isMacOS()) { inputHandler.addKeyBinding("M+LEFT", InputHandler.HOME); inputHandler.addKeyBinding("M+RIGHT", InputHandler.END); + inputHandler.addKeyBinding("MS+LEFT", InputHandler.SELECT_HOME); // 0122 + inputHandler.addKeyBinding("MS+RIGHT", InputHandler.SELECT_END); // 0122 + } else { + inputHandler.addKeyBinding("C+LEFT", InputHandler.HOME); // 0122 + inputHandler.addKeyBinding("C+RIGHT", InputHandler.END); // 0122 + inputHandler.addKeyBinding("CS+HOME", InputHandler.SELECT_HOME); // 0122 + inputHandler.addKeyBinding("CS+END", InputHandler.SELECT_END); // 0122 } - inputHandler.addKeyBinding("S+HOME", InputHandler.SELECT_HOME); - inputHandler.addKeyBinding("S+END", InputHandler.SELECT_END); - inputHandler.addKeyBinding(mod + "+HOME", InputHandler.DOCUMENT_HOME); - inputHandler.addKeyBinding(mod + "+END", InputHandler.DOCUMENT_END); - inputHandler.addKeyBinding(mod + "S+HOME", InputHandler.SELECT_DOC_HOME); - inputHandler.addKeyBinding(mod + "S+END", InputHandler.SELECT_DOC_END); - inputHandler.addKeyBinding("PAGE_UP", InputHandler.PREV_PAGE); inputHandler.addKeyBinding("PAGE_DOWN", InputHandler.NEXT_PAGE); inputHandler.addKeyBinding("S+PAGE_UP", InputHandler.SELECT_PREV_PAGE); @@ -80,6 +104,7 @@ public class PdeTextAreaDefaults extends TextAreaDefaults { inputHandler.addKeyBinding("S+RIGHT", InputHandler.SELECT_NEXT_CHAR); inputHandler.addKeyBinding(mod + "+RIGHT", InputHandler.NEXT_WORD); inputHandler.addKeyBinding(mod + "S+RIGHT", InputHandler.SELECT_NEXT_WORD); + inputHandler.addKeyBinding("UP", InputHandler.PREV_LINE); inputHandler.addKeyBinding(mod + "+UP", InputHandler.PREV_LINE); // p5 inputHandler.addKeyBinding("S+UP", InputHandler.SELECT_PREV_LINE); @@ -87,6 +112,11 @@ public class PdeTextAreaDefaults extends TextAreaDefaults { inputHandler.addKeyBinding(mod + "+DOWN", InputHandler.NEXT_LINE); // p5 inputHandler.addKeyBinding("S+DOWN", InputHandler.SELECT_NEXT_LINE); + inputHandler.addKeyBinding("MS+UP", InputHandler.SELECT_DOC_HOME); + inputHandler.addKeyBinding("CS+UP", InputHandler.SELECT_DOC_HOME); + inputHandler.addKeyBinding("MS+DOWN", InputHandler.SELECT_DOC_END); + inputHandler.addKeyBinding("CS+DOWN", InputHandler.SELECT_DOC_END); + inputHandler.addKeyBinding(mod + "+ENTER", InputHandler.REPEAT); document = new SyntaxDocument(); diff --git a/app/syntax/TextAreaPainter.java b/app/syntax/TextAreaPainter.java index b1e4ccc82..80487b820 100644 --- a/app/syntax/TextAreaPainter.java +++ b/app/syntax/TextAreaPainter.java @@ -18,6 +18,7 @@ import javax.swing.text.*; import javax.swing.JComponent; import java.awt.event.MouseEvent; import java.awt.*; +import java.awt.print.*; /** * The text area repaint manager. It performs double buffering and paints @@ -25,8 +26,12 @@ import java.awt.*; * @author Slava Pestov * @version $Id$ */ -public class TextAreaPainter extends JComponent implements TabExpander +public class TextAreaPainter extends JComponent +implements TabExpander, Printable { + /** True if inside printing, will handle disabling the highlight */ + boolean printing; + /** * Creates a new repaint manager. This should be not be called * directly. @@ -405,6 +410,32 @@ public class TextAreaPainter extends JComponent implements TabExpander } } + + public int print(Graphics g, PageFormat pageFormat, int pageIndex) { + int lineHeight = fm.getHeight(); + int linesPerPage = (int) (pageFormat.getImageableHeight() / lineHeight); + int lineCount = textArea.getLineCount(); + int lastPage = lineCount / linesPerPage; + + if (pageIndex > lastPage) { + return NO_SUCH_PAGE; + + } else { + Graphics2D g2d = (Graphics2D)g; + TokenMarker tokenMarker = textArea.getDocument().getTokenMarker(); + int firstLine = pageIndex*linesPerPage; + g2d.translate(Math.max(54, pageFormat.getImageableX()), + pageFormat.getImageableY() - firstLine*lineHeight); + printing = true; + for (int line = firstLine; line < firstLine + linesPerPage; line++) { + paintLine(g2d, tokenMarker, line, 0); + } + printing = false; + return PAGE_EXISTS; + } + } + + /** * Marks a line as needing a repaint. * @param line The line to invalidate @@ -600,6 +631,7 @@ public class TextAreaPainter extends JComponent implements TabExpander protected void paintHighlight(Graphics gfx, int line, int y) { + if (!printing) { if (line >= textArea.getSelectionStartLine() && line <= textArea.getSelectionEndLine()) paintLineHighlight(gfx,line,y); @@ -613,6 +645,7 @@ public class TextAreaPainter extends JComponent implements TabExpander if (line == textArea.getCaretLine()) paintCaret(gfx,line,y); } + } protected void paintLineHighlight(Graphics gfx, int line, int y) { diff --git a/app/tools/Archiver.java b/app/tools/Archiver.java index facf49dd5..0c90e5e69 100755 --- a/app/tools/Archiver.java +++ b/app/tools/Archiver.java @@ -25,6 +25,7 @@ package processing.app.tools; import processing.app.*; +import java.awt.FileDialog; import java.io.*; import java.text.*; import java.util.*; @@ -35,7 +36,7 @@ public class Archiver { Editor editor; // someday these will be settable - boolean useDate = true; //false; + boolean useDate; int digits = 3; NumberFormat numberFormat; @@ -79,6 +80,9 @@ public class Archiver { String namely = null; int index = 0; do { + // only use the date if the sketch name isn't the default name + useDate = !name.startsWith("sketch_"); + if (useDate) { String purty = dateFormat.format(new Date()); String stamp = purty + ((char) ('a' + index)); @@ -93,6 +97,20 @@ public class Archiver { index++; } while (newbie.exists()); + // open up a prompt for where to save this fella + FileDialog fd = + new FileDialog(editor, "Archive sketch as:", FileDialog.SAVE); + fd.setDirectory(parent.getAbsolutePath()); + fd.setFile(newbie.getName()); + fd.show(); + + String directory = fd.getDirectory(); + String filename = fd.getFile(); + + // only write the file if not canceled + if (filename != null) { + newbie = new File(directory, filename); + try { //System.out.println(newbie); FileOutputStream zipOutputFile = new FileOutputStream(newbie); @@ -110,6 +128,9 @@ public class Archiver { } catch (IOException e) { e.printStackTrace(); } + } else { + editor.message("Archive sketch canceled."); + } } diff --git a/app/tools/AutoFormat.java b/app/tools/AutoFormat.java index a24bc6021..8d95c1645 100644 --- a/app/tools/AutoFormat.java +++ b/app/tools/AutoFormat.java @@ -24,7 +24,7 @@ package processing.app.tools; import processing.app.*; -//import processing.core.*; +import processing.core.*; import java.io.*; import java.util.StringTokenizer; @@ -915,26 +915,25 @@ public class AutoFormat { if (formattedText.equals(originalText)) { editor.message("No changes necessary for Auto Format."); - } else { - // replace with new bootiful text - // selectionEnd hopefully at least in the neighborhood - editor.setText(formattedText, selectionEnd, selectionEnd); - editor.sketch.setModified(true); - + } else if (paren != 0) { // warn user if there are too many parens in either direction - if (paren != 0) { - editor.error("Warning: Too many " + + editor.error("Auto Format Canceled: Too many " + ((paren < 0) ? "right" : "left") + " parentheses."); } else if (c_level != 0) { // check braces only if parens are ok - editor.error("Warning: Too many " + + editor.error("Auto Format Canceled: Too many " + ((c_level < 0) ? "right" : "left") + " curly braces."); + } else { + // replace with new bootiful text + // selectionEnd hopefully at least in the neighborhood + editor.setText(formattedText, selectionEnd, selectionEnd); + editor.sketch.setModified(true); + // mark as finished editor.message("Auto Format finished."); } - } } catch (Exception e) { editor.error(e); diff --git a/app/tools/DiscourseFormat.java b/app/tools/DiscourseFormat.java new file mode 100644 index 000000000..23e90ab1e --- /dev/null +++ b/app/tools/DiscourseFormat.java @@ -0,0 +1,313 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2005-06 Ignacio Manuel González Moreta + Copyright (c) 2006 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.app.tools; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.text.Segment; + +import processing.app.*; +import processing.app.syntax.*; +import processing.core.PApplet; + +/** + * Format for Discourse Tool + *

+ * Original code by owd. + * Revised and updated for revision 0108 by Ben Fry (10 March 2006). + * This code will later be removed but is included with release 0108+ + * while features for the "Tools" menu are in testing. + *

+ * Updated for 0122 to simply copy the code directly to the clipboard, + * rather than opening a new window. + *

+ * Notes from the original source: + * Discourse.java This is a dirty-mix source. + * NOTE that: No macs and no keyboard. Unreliable source. + * Only format processing code using fontMetrics. + * It works under my windows XP + PentiumIV + Processing 0091. + */ +public class DiscourseFormat { + + //static final String WINDOW_TITLE = "Format for Discourse by owd"; + + // p5 icon for the window + //static Image icon; + + Editor editor; + //JEditTextArea textarea; + + // JTextArea of the actual Editor + JEditTextArea parent; + + //JFrame frame; + + /** + * Creates a new window with the formated (YaBB tags) sketchcode + * from the actual Processing Tab ready to send to the processing discourse + * web (copy & paste) + */ + public DiscourseFormat(Editor editor) { + this.editor = editor; + this.parent = editor.textarea; + + /* + textarea = new JEditTextArea(new PdeTextAreaDefaults()); + textarea.setRightClickPopup(new DiscourseTextAreaPopup()); + textarea.setTokenMarker(new PdeKeywords()); + textarea.setHorizontalOffset(6); + + textarea.setEditable(false); + + // Create and set up the window. + frame = new JFrame(WINDOW_TITLE); + frame.setSize(500, 500); + + // set the window icon + try { + icon = Base.getImage("icon.gif", frame); + frame.setIconImage(icon); + } catch (Exception e) { } // fail silently, no big whup + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + Container pain = frame.getContentPane(); + pain.setLayout(new BorderLayout()); + pain.add(textarea, BorderLayout.CENTER); + + frame.setResizable(true); + + frame.pack(); + frame.setLocation(100, 100); + //frame.setVisible(true); + */ + } + + + public void show() { + // Format and render sketchcode + + // [code] tag cancels other tags, using [quote] + StringBuffer cf = new StringBuffer("[quote] \n \n"); + + // Line by line + for (int i = 0; i < parent.getLineCount(); i++) { + cf.append(formatCode(i)); + } + + cf.append("\n [/quote]"); + + /* + // Send the text to the textarea + textarea.setText(cf.toString()); + textarea.select(0, 0); + + frame.show(); + */ + + StringSelection formatted = new StringSelection(cf.toString()); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(formatted, new ClipboardOwner() { + public void lostOwnership(Clipboard clipboard, Transferable contents) { + // i don't care about ownership + } + }); + + editor.message("Discourse-formatted code has been " + + "copied to the clipboard."); + } + + + // A terrible headache... + public String formatCode(int line) { + StringBuffer cf = new StringBuffer(); + + // Segment + Segment lineSegment = new Segment(); + + TextAreaPainter painter = parent.getPainter(); + TokenMarker tokenMarker = parent.getTokenMarker(); + + // Use painter's cached info for speed + FontMetrics fm = painter.getFontMetrics(); + + // get line text from parent textarea + parent.getLineText(line, lineSegment); + + char[] segmentArray = lineSegment.array; + int limit = lineSegment.getEndIndex(); + int segmentOffset = lineSegment.offset; + int segmentCount = lineSegment.count; + int width = 0; //parent.getHorizontalOffset(); + + int x = 0; //parent.getHorizontalOffset(); + + // If syntax coloring is disabled, do simple translation + if (tokenMarker == null) { + for (int j = 0; j < segmentCount; j++) { + char c = segmentArray[j + segmentOffset]; + cf = cf.append(c); //concat(character(c)); + int charWidth; + if (c == '\t') { + charWidth = (int) painter.nextTabStop(width, j) - width; + } else { + charWidth = fm.charWidth(c); + } + width += charWidth; + } + + } else { + // If syntax coloring is enabled, we have to do this + // because tokens can vary in width + Token tokens; + if ((painter.getCurrentLineIndex() == line) && + (painter.getCurrentLineTokens() != null)) { + tokens = painter.getCurrentLineTokens(); + + } else { + painter.setCurrentLineIndex(line); + //painter.currentLineIndex = line; + painter.setCurrentLineTokens(tokenMarker.markTokens(lineSegment, line)); + tokens = painter.getCurrentLineTokens(); + } + + int offset = 0; + Toolkit toolkit = painter.getToolkit(); + Font defaultFont = painter.getFont(); + SyntaxStyle[] styles = painter.getStyles(); + + for (;;) { + byte id = tokens.id; + if (id == Token.END) { + char c = segmentArray[segmentOffset + offset]; + if (segmentOffset + offset < limit) { + cf.append(c); + } else { + cf.append('\n'); + } + return cf.toString(); + } + if (id == Token.NULL) { + fm = painter.getFontMetrics(); + } else { + // Place open tags [] + //cf.append("[color=" + color() + "]"); + cf.append("[color=#"); + cf.append(PApplet.hex(styles[id].getColor().getRGB() & 0xFFFFFF, 6)); + cf.append("]"); + + if (styles[id].isBold()) + cf.append("[b]"); + + fm = styles[id].getFontMetrics(defaultFont); + } + int length = tokens.length; + + for (int j = 0; j < length; j++) { + char c = segmentArray[segmentOffset + offset + j]; + cf.append(c); + // Place close tags [/] + if (j == (length - 1) && id != Token.NULL && styles[id].isBold()) + cf.append("[/b]"); + if (j == (length - 1) && id != Token.NULL) + cf.append("[/color]"); + int charWidth; + if (c == '\t') { + charWidth = (int) painter + .nextTabStop(width, offset + j) + - width; + } else { + charWidth = fm.charWidth(c); + } + width += charWidth; + } + offset += length; + tokens = tokens.next; + } + } + return cf.toString(); + } + + + /** + * Returns the discourse popup menu. Another features can be added: format + * selected text with a determinated tag (I'm thinking about [url]selected + * text[/url]) + */ + /* + class DiscourseTextAreaPopup extends JPopupMenu { + JMenuItem copyItem; + + public DiscourseTextAreaPopup() { + JMenuItem item; + + copyItem = new JMenuItem("Copy"); + copyItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.copy(); + } + }); + this.add(copyItem); + + item = new JMenuItem("Select All"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.selectAll(); + } + }); + this.add(item); + } + + // if no text is selected, disable copy menu item + public void show(Component component, int x, int y) { + if (textarea.isSelectionActive()) { + copyItem.setEnabled(true); + + } else { + copyItem.setEnabled(false); + } + super.show(component, x, y); + } + } + */ + + + /* + // A false listener (use the mouse) + public class DiscourseListener { + + public DiscourseListener(JEditTextArea thisTextarea) { + // I'm a... I know this gives peoblems, but all this code + // is a funny hacking experiment + thisTextarea.editorListener = parent.editorListener; + } + + public boolean keyPressed(KeyEvent event) { + System.out.println("Is your mouse lone some tonight..."); + return false; + } + } + */ +} \ No newline at end of file diff --git a/app/tools/ExportFolder.java b/app/tools/ExportFolder.java index e4e7492bb..bfe9e9d64 100755 --- a/app/tools/ExportFolder.java +++ b/app/tools/ExportFolder.java @@ -99,6 +99,8 @@ public class ExportFolder { // skip .DS_Store files, etc if (!folder.isDirectory()) return; // false; + System.out.println(folder.getAbsolutePath()); + String list[] = folder.list(); // if a bad folder or something like that, this might come back null if (list == null) return; // false; diff --git a/build/macosx/Arduino.xcodeproj/project.pbxproj b/build/macosx/Arduino.xcodeproj/project.pbxproj index 00867cd76..d73263d7a 100644 --- a/build/macosx/Arduino.xcodeproj/project.pbxproj +++ b/build/macosx/Arduino.xcodeproj/project.pbxproj @@ -151,6 +151,7 @@ $JAVAROOT/mrj.jar $JAVAROOT/registry.jar $JAVAROOT/RXTXcomm.jar + $JAVAROOT/quaqua.jar JVMVersion 1.4+ @@ -170,6 +171,22 @@ /* Begin PBXBuildFile section */ 332D4DB609CF147F00BF81F6 /* Sizer.java in Sources */ = {isa = PBXBuildFile; fileRef = 332D4DB509CF147F00BF81F6 /* Sizer.java */; }; + 335A28F50C8CCB0A00D8A7F4 /* quaqua.jar in CopyFiles */ = {isa = PBXBuildFile; fileRef = 335A28F30C8CCAF700D8A7F4 /* quaqua.jar */; }; + 335A28FE0C8CCB4000D8A7F4 /* libquaqua.jnilib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 335A28F20C8CCAF700D8A7F4 /* libquaqua.jnilib */; }; + 335A29140C8CCC0900D8A7F4 /* PApplet.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A29070C8CCC0900D8A7F4 /* PApplet.java */; }; + 335A29150C8CCC0900D8A7F4 /* PConstants.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A29080C8CCC0900D8A7F4 /* PConstants.java */; }; + 335A29160C8CCC0900D8A7F4 /* PFont.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A29090C8CCC0900D8A7F4 /* PFont.java */; }; + 335A29170C8CCC0900D8A7F4 /* PGraphics.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A290A0C8CCC0900D8A7F4 /* PGraphics.java */; }; + 335A29180C8CCC0900D8A7F4 /* PGraphics2D.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A290B0C8CCC0900D8A7F4 /* PGraphics2D.java */; }; + 335A29190C8CCC0900D8A7F4 /* PGraphics3D.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A290C0C8CCC0900D8A7F4 /* PGraphics3D.java */; }; + 335A291A0C8CCC0900D8A7F4 /* PGraphicsJava2D.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A290D0C8CCC0900D8A7F4 /* PGraphicsJava2D.java */; }; + 335A291B0C8CCC0900D8A7F4 /* PImage.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A290E0C8CCC0900D8A7F4 /* PImage.java */; }; + 335A291C0C8CCC0900D8A7F4 /* PLine.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A290F0C8CCC0900D8A7F4 /* PLine.java */; }; + 335A291D0C8CCC0900D8A7F4 /* PMatrix.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A29100C8CCC0900D8A7F4 /* PMatrix.java */; }; + 335A291E0C8CCC0900D8A7F4 /* PPolygon.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A29110C8CCC0900D8A7F4 /* PPolygon.java */; }; + 335A291F0C8CCC0900D8A7F4 /* PShape.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A29120C8CCC0900D8A7F4 /* PShape.java */; }; + 335A29200C8CCC0900D8A7F4 /* PTriangle.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A29130C8CCC0900D8A7F4 /* PTriangle.java */; }; + 335A29240C8CCC5E00D8A7F4 /* DiscourseFormat.java in Sources */ = {isa = PBXBuildFile; fileRef = 335A29230C8CCC5E00D8A7F4 /* DiscourseFormat.java */; }; 335D3B010C4EE1B80065B27E /* ATmegaBOOT_168.c in CopyFiles */ = {isa = PBXBuildFile; fileRef = 335D3AFD0C4EE19D0065B27E /* ATmegaBOOT_168.c */; }; 335D3B020C4EE1B80065B27E /* ATmegaBOOT_168_diecimila.hex in CopyFiles */ = {isa = PBXBuildFile; fileRef = 335D3AFE0C4EE19D0065B27E /* ATmegaBOOT_168_diecimila.hex */; }; 335D3B030C4EE1B80065B27E /* ATmegaBOOT_168_ng.hex in CopyFiles */ = {isa = PBXBuildFile; fileRef = 335D3AFF0C4EE19D0065B27E /* ATmegaBOOT_168_ng.hex */; }; @@ -363,6 +380,7 @@ dstPath = ""; dstSubfolderSpec = 15; files = ( + 335A28F50C8CCB0A00D8A7F4 /* quaqua.jar in CopyFiles */, 33CF03CC09662DC000F2C9A9 /* mrj.jar in CopyFiles */, 33CF03CD09662DC000F2C9A9 /* RXTXcomm.jar in CopyFiles */, 33CF03CE09662DC000F2C9A9 /* antlr.jar in CopyFiles */, @@ -424,6 +442,7 @@ dstPath = ""; dstSubfolderSpec = 16; files = ( + 335A28FE0C8CCB4000D8A7F4 /* libquaqua.jnilib in CopyFiles */, 339514FA097AEB8000193C89 /* license.txt in CopyFiles */, 339514FB097AEB8000193C89 /* readme.txt in CopyFiles */, ); @@ -433,6 +452,22 @@ /* Begin PBXFileReference section */ 332D4DB509CF147F00BF81F6 /* Sizer.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; path = Sizer.java; sourceTree = ""; }; + 335A28F20C8CCAF700D8A7F4 /* libquaqua.jnilib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libquaqua.jnilib; sourceTree = ""; }; + 335A28F30C8CCAF700D8A7F4 /* quaqua.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = quaqua.jar; sourceTree = ""; }; + 335A29070C8CCC0900D8A7F4 /* PApplet.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PApplet.java; sourceTree = ""; }; + 335A29080C8CCC0900D8A7F4 /* PConstants.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PConstants.java; sourceTree = ""; }; + 335A29090C8CCC0900D8A7F4 /* PFont.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PFont.java; sourceTree = ""; }; + 335A290A0C8CCC0900D8A7F4 /* PGraphics.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PGraphics.java; sourceTree = ""; }; + 335A290B0C8CCC0900D8A7F4 /* PGraphics2D.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PGraphics2D.java; sourceTree = ""; }; + 335A290C0C8CCC0900D8A7F4 /* PGraphics3D.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PGraphics3D.java; sourceTree = ""; }; + 335A290D0C8CCC0900D8A7F4 /* PGraphicsJava2D.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PGraphicsJava2D.java; sourceTree = ""; }; + 335A290E0C8CCC0900D8A7F4 /* PImage.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PImage.java; sourceTree = ""; }; + 335A290F0C8CCC0900D8A7F4 /* PLine.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PLine.java; sourceTree = ""; }; + 335A29100C8CCC0900D8A7F4 /* PMatrix.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PMatrix.java; sourceTree = ""; }; + 335A29110C8CCC0900D8A7F4 /* PPolygon.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PPolygon.java; sourceTree = ""; }; + 335A29120C8CCC0900D8A7F4 /* PShape.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PShape.java; sourceTree = ""; }; + 335A29130C8CCC0900D8A7F4 /* PTriangle.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PTriangle.java; sourceTree = ""; }; + 335A29230C8CCC5E00D8A7F4 /* DiscourseFormat.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = DiscourseFormat.java; sourceTree = ""; }; 335D3AFD0C4EE19D0065B27E /* ATmegaBOOT_168.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = ATmegaBOOT_168.c; sourceTree = ""; }; 335D3AFE0C4EE19D0065B27E /* ATmegaBOOT_168_diecimila.hex */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = ATmegaBOOT_168_diecimila.hex; sourceTree = ""; }; 335D3AFF0C4EE19D0065B27E /* ATmegaBOOT_168_ng.hex */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = ATmegaBOOT_168_ng.hex; sourceTree = ""; }; @@ -576,6 +611,27 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 335A29060C8CCC0800D8A7F4 /* core */ = { + isa = PBXGroup; + children = ( + 335A29070C8CCC0900D8A7F4 /* PApplet.java */, + 335A29080C8CCC0900D8A7F4 /* PConstants.java */, + 335A29090C8CCC0900D8A7F4 /* PFont.java */, + 335A290A0C8CCC0900D8A7F4 /* PGraphics.java */, + 335A290B0C8CCC0900D8A7F4 /* PGraphics2D.java */, + 335A290C0C8CCC0900D8A7F4 /* PGraphics3D.java */, + 335A290D0C8CCC0900D8A7F4 /* PGraphicsJava2D.java */, + 335A290E0C8CCC0900D8A7F4 /* PImage.java */, + 335A290F0C8CCC0900D8A7F4 /* PLine.java */, + 335A29100C8CCC0900D8A7F4 /* PMatrix.java */, + 335A29110C8CCC0900D8A7F4 /* PPolygon.java */, + 335A29120C8CCC0900D8A7F4 /* PShape.java */, + 335A29130C8CCC0900D8A7F4 /* PTriangle.java */, + ); + name = core; + path = ../../core; + sourceTree = SOURCE_ROOT; + }; 335D3AFC0C4EE19D0065B27E /* bootloader168 */ = { isa = PBXGroup; children = ( @@ -660,6 +716,7 @@ 33FFFD3D0965B1E40016AC38 = { isa = PBXGroup; children = ( + 335A29060C8CCC0800D8A7F4 /* core */, 33CF03B009662CA800F2C9A9 /* arduino.icns */, 33FF07D50965C3560016AC38 /* targets */, 33FFFE220965BD100016AC38 /* app */, @@ -789,6 +846,7 @@ 33FFFE710965BD110016AC38 /* tools */ = { isa = PBXGroup; children = ( + 335A29230C8CCC5E00D8A7F4 /* DiscourseFormat.java */, 33BEDDD309D6E8D800430D5B /* Archiver.java */, 33BEDDD409D6E8D800430D5B /* ExportFolder.java */, 33FFFE720965BD110016AC38 /* AutoFormat.java */, @@ -843,6 +901,8 @@ 33FFFEAC0965BD110016AC38 /* dist */ = { isa = PBXGroup; children = ( + 335A28F20C8CCAF700D8A7F4 /* libquaqua.jnilib */, + 335A28F30C8CCAF700D8A7F4 /* quaqua.jar */, 33FFFEAE0965BD110016AC38 /* bootloader */, 33FFFEB20965BD110016AC38 /* drivers */, 33FFFEB50965BD110016AC38 /* DS_Store */, @@ -1065,6 +1125,20 @@ 33BEE0CE09D7446100430D5B /* Library.java in Sources */, 33F9446D0C2B2F6F0093EB9C /* UispUploader.java in Sources */, 33F944E10C2B33560093EB9C /* AvrdudeUploader.java in Sources */, + 335A29140C8CCC0900D8A7F4 /* PApplet.java in Sources */, + 335A29150C8CCC0900D8A7F4 /* PConstants.java in Sources */, + 335A29160C8CCC0900D8A7F4 /* PFont.java in Sources */, + 335A29170C8CCC0900D8A7F4 /* PGraphics.java in Sources */, + 335A29180C8CCC0900D8A7F4 /* PGraphics2D.java in Sources */, + 335A29190C8CCC0900D8A7F4 /* PGraphics3D.java in Sources */, + 335A291A0C8CCC0900D8A7F4 /* PGraphicsJava2D.java in Sources */, + 335A291B0C8CCC0900D8A7F4 /* PImage.java in Sources */, + 335A291C0C8CCC0900D8A7F4 /* PLine.java in Sources */, + 335A291D0C8CCC0900D8A7F4 /* PMatrix.java in Sources */, + 335A291E0C8CCC0900D8A7F4 /* PPolygon.java in Sources */, + 335A291F0C8CCC0900D8A7F4 /* PShape.java in Sources */, + 335A29200C8CCC0900D8A7F4 /* PTriangle.java in Sources */, + 335A29240C8CCC5E00D8A7F4 /* DiscourseFormat.java in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/build/macosx/dist/libquaqua.jnilib b/build/macosx/dist/libquaqua.jnilib new file mode 100755 index 000000000..4c552def8 Binary files /dev/null and b/build/macosx/dist/libquaqua.jnilib differ diff --git a/build/macosx/dist/quaqua.jar b/build/macosx/dist/quaqua.jar new file mode 100755 index 000000000..d45b0aac4 Binary files /dev/null and b/build/macosx/dist/quaqua.jar differ diff --git a/build/shared/dist/examples/Digital/Loop/Loop.pde b/build/shared/dist/examples/Digital/Loop/Loop.pde index 8d44cd90f..5ea723127 100644 --- a/build/shared/dist/examples/Digital/Loop/Loop.pde +++ b/build/shared/dist/examples/Digital/Loop/Loop.pde @@ -30,8 +30,8 @@ void loop() digitalWrite(pins[i], LOW); // and turning it off. } for (i = num_pins - 1; i >= 0; i--) { - digitalWrite(i, HIGH); + digitalWrite(pins[i], HIGH); delay(timer); - digitalWrite(i, LOW); + digitalWrite(pins[i], LOW); } } diff --git a/build/windows/dist/avr_tools.zip b/build/windows/dist/avr_tools.zip index c85df3d43..fd8bf0144 100644 Binary files a/build/windows/dist/avr_tools.zip and b/build/windows/dist/avr_tools.zip differ diff --git a/build/windows/make.sh b/build/windows/make.sh index 9863d3f9c..85e5f3bd3 100755 --- a/build/windows/make.sh +++ b/build/windows/make.sh @@ -145,8 +145,8 @@ CLASSPATH="..\\build\\windows\\work\\lib\\RXTXcomm.jar;..\\build\\windows\\work\ # show the user an error, rather than crapping out with some strange # "class not found" crap # need to do this twice because otherwise dependencies aren't resolved right. -../build/windows/work/jikes -target 1.3 +D -classpath "$CLASSPATH;..\\build\\windows\\work\\classes" -d ..\\build\\windows\\work\\classes preproc/*.java syntax/*.java tools/*.java *.java -../build/windows/work/jikes -target 1.3 +D -classpath "$CLASSPATH;..\\build\\windows\\work\\classes" -d ..\\build\\windows\\work\\classes preproc/*.java syntax/*.java tools/*.java *.java +../build/windows/work/jikes -target 1.3 +D -classpath "$CLASSPATH;..\\build\\windows\\work\\classes" -d ..\\build\\windows\\work\\classes ../core/*.java preproc/*.java syntax/*.java tools/*.java *.java +../build/windows/work/jikes -target 1.3 +D -classpath "$CLASSPATH;..\\build\\windows\\work\\classes" -d ..\\build\\windows\\work\\classes ../core/*.java preproc/*.java syntax/*.java tools/*.java *.java cd ../build/windows/work/classes rm -f ../lib/pde.jar diff --git a/core/PApplet.java b/core/PApplet.java new file mode 100644 index 000000000..b6616cea0 --- /dev/null +++ b/core/PApplet.java @@ -0,0 +1,8334 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.applet.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.text.*; +import java.util.*; +import java.util.zip.*; + + +/** + * Base class for all sketches that use processing.core. + *

+ * Note that you should not use AWT or Swing components inside a Processing + * applet. The surface is made to automatically update itself, and will cause + * problems with redraw of components drawn above it. If you'd like to + * integrate other Java components, see below. + *

+ * This class extends Applet instead of JApplet because 1) we will eventually + * be returning Java 1.1 support, which does not include Swing (without an + * additional, sizable, download), and 2) Swing is a bloated piece of crap. + * A Processing applet is a heavyweight AWT component, and can be used the + * same as any other AWT component, with or without Swing. + *

+ * Similarly, Processing runs in a Frame and not a JFrame. However, there's + * nothing to prevent you from embedding a PApplet into a JFrame, it's just + * that the base version uses a regular AWT frame because there's simply + * no need for swing in that context. If people want to use Swing, they can + * embed themselves as they wish. + *

+ * It is possible to use PApplet, along with core.jar in other projects. + * In addition to enabling you to use Java 1.5+ features with your sketch, + * this also allows you to embed a Processing drawing area into another Java + * application. This means you can use standard GUI controls with a Processing + * sketch. Because AWT and Swing GUI components cannot be used on top of a + * PApplet, you can instead embed the PApplet inside another GUI the wayyou + * would any other Component. + *

+ * Because the default animation thread will run at 60 frames per second, + * an embedded PApplet can make the parent sluggish. You can use frameRate() + * to make it update less often, or you can use noLoop() and loop() to disable + * and then re-enable looping. If you want to only update the sketch + * intermittently, use noLoop() inside setup(), and redraw() whenever + * the screen needs to be updated once (or loop() to re-enable the animation + * thread). The following example embeds a sketch and also uses the noLoop() + * and redraw() methods. You need not use noLoop() and redraw() when embedding + * if you want your application to animate continuously. + *

+ * public class ExampleFrame extends Frame {
+ *
+ *     public ExampleFrame() {
+ *         super("Embedded PApplet");
+ *
+ *         setLayout(new BorderLayout());
+ *         PApplet embed = new Embedded();
+ *         add(embed, BorderLayout.CENTER);
+ *
+ *         // important to call this whenever embedding a PApplet.
+ *         // It ensures that the animation thread is started and
+ *         // that other internal variables are properly set.
+ *         embed.init();
+ *     }
+ * }
+ *
+ * public class Embedded extends PApplet {
+ *
+ *     public void setup() {
+ *         // original setup code here ...
+ *         size(400, 400);
+ *
+ *         // prevent thread from starving everything else
+ *         noLoop();
+ *     }
+ *
+ *     public void draw() {
+ *         // drawing code goes here
+ *     }
+ *
+ *     public void mousePressed() {
+ *         // do something based on mouse movement
+ *
+ *         // update the screen (run draw once)
+ *         redraw();
+ *     }
+ * }
+ * 
+ * + *

Processing on multiple displays

+ *

I was asked about Processing with multiple displays, and for lack of a + * better place to document it, things will go here.

+ *

You can address both screens by making a window the width of both, + * and the height of the maximum of both screens. In this case, do not use + * present mode, because that's exclusive to one screen. Basically it'll + * give you a PApplet that spans both screens. If using one half to control + * and the other half for graphics, you'd just have to put the 'live' stuff + * on one half of the canvas, the control stuff on the other. This works + * better in windows because on the mac we can't get rid of the menu bar + * unless it's running in present mode.

+ *

For more control, you need to write straight java code that uses p5. + * You can create two windows, that are shown on two separate screens, + * that have their own PApplet. this is just one of the tradeoffs of one of + * the things that we don't support in p5 from within the environment + * itself (we must draw the line somewhere), because of how messy it would + * get to start talking about multiple screens. It's also not that tough to + * do by hand w/ some Java code.

+ */ +public class PApplet extends Applet + implements PConstants, Runnable, + MouseListener, MouseMotionListener, KeyListener, FocusListener +{ + /** + * Full name of the Java version (i.e. 1.5.0_11). + * Prior to 0125, this was only the first three digits. + */ + public static final String javaVersionName = + System.getProperty("java.version"); + + /** + * Version of Java that's in use, whether 1.1 or 1.3 or whatever, + * stored as a float. + *

+ * Note that because this is stored as a float, the values may + * not be exactly 1.3 or 1.4. Instead, make sure you're + * comparing against 1.3f or 1.4f, which will have the same amount + * of error (i.e. 1.40000001). This could just be a double, but + * since Processing only uses floats, it's safer for this to be a float + * because there's no good way to specify a double with the preproc. + */ + public static final float javaVersion = + new Float(javaVersionName.substring(0, 3)).floatValue(); + + /** + * Current platform in use, one of the + * PConstants WINDOWS, MACOSX, MACOS9, LINUX or OTHER. + */ + static public int platform; + + /** + * Current platform in use. + *

+ * Equivalent to System.getProperty("os.name"), just used internally. + */ + static public String platformName = + System.getProperty("os.name"); + + static { + // figure out which operating system + // this has to be first, since editor needs to know + + if (platformName.toLowerCase().indexOf("mac") != -1) { + // can only check this property if running on a mac + // on a pc it throws a security exception and kills the applet + // (but on the mac it does just fine) + if (System.getProperty("mrj.version") != null) { // running on a mac + platform = (platformName.equals("Mac OS X")) ? + MACOSX : MACOS9; + } + + } else { + String osname = System.getProperty("os.name"); + + if (osname.indexOf("Windows") != -1) { + platform = WINDOWS; + + } else if (osname.equals("Linux")) { // true for the ibm vm + platform = LINUX; + + } else { + platform = OTHER; + } + } + } + + + /** The PGraphics renderer associated with this PApplet */ + public PGraphics g; + + //protected Object glock = new Object(); // for sync + + /** The frame containing this applet (if any) */ + public Frame frame; + + /** + * Message of the Exception thrown when size() is called the first time. + *

+ * This is used internally so that setup() is forced to run twice + * when the renderer is changed. This is the only way for us to handle + * invoking the new renderer while also in the midst of rendering. + */ + static final String NEW_RENDERER = "new renderer"; + + /** + * The screen size when the applet was started. + *

+ * Access this via screen.width and screen.height. To make an applet + * run at full screen, use size(screen.width, screen.height). + *

+ * If you have multiple displays, this will be the size of the main + * display. Running full screen across multiple displays isn't + * particularly supported, and requires more monkeying with the values. + * This probably can't/won't be fixed until/unless I get a dual head + * system. + *

+ * Note that this won't update if you change the resolution + * of your screen once the the applet is running. + */ + public Dimension screen = + Toolkit.getDefaultToolkit().getScreenSize(); + + /** + * A leech graphics object that is echoing all events. + */ + public PGraphics recorder; + + /** + * Command line options passed in from main(). + *

+ * This does not include the arguments passed in to PApplet itself. + */ + public String args[]; + + /** Path to sketch folder */ + public String sketchPath; //folder; + + /** When debugging headaches */ + static final boolean THREAD_DEBUG = false; + + private Object blocker = new Object(); + + /** Default width and height for applet when not specified */ + static public final int DEFAULT_WIDTH = 100; + static public final int DEFAULT_HEIGHT = 100; + + /** + * Minimum dimensions for the window holding an applet. + * This varies between platforms, Mac OS X 10.3 can do any height + * but requires at least 128 pixels width. Windows XP has another + * set of limitations. And for all I know, Linux probably lets you + * make windows with negative sizes. + */ + static public final int MIN_WINDOW_WIDTH = 128; + static public final int MIN_WINDOW_HEIGHT = 128; + + /** + * true if no size() command has been executed. This is used to wait until + * a size has been set before placing in the window and showing it. + */ + public boolean defaultSize; + + /** + * Pixel buffer from this applet's PGraphics. + *

+ * When used with OpenGL or Java2D, this value will + * be null until loadPixels() has been called. + */ + public int pixels[]; + + /** width of this applet's associated PGraphics */ + public int width; + + /** height of this applet's associated PGraphics */ + public int height; + + /** current x position of the mouse */ + public int mouseX; + + /** current y position of the mouse */ + public int mouseY; + + /** + * Previous x/y position of the mouse. This will be a different value + * when inside a mouse handler (like the mouseMoved() method) versus + * when inside draw(). Inside draw(), pmouseX is updated once each + * frame, but inside mousePressed() and friends, it's updated each time + * an event comes through. Be sure to use only one or the other type of + * means for tracking pmouseX and pmouseY within your sketch, otherwise + * you're gonna run into trouble. + */ + public int pmouseX, pmouseY; + + /** + * previous mouseX/Y for the draw loop, separated out because this is + * separate from the pmouseX/Y when inside the mouse event handlers. + */ + protected int dmouseX, dmouseY; + + /** + * pmouseX/Y for the event handlers (mousePressed(), mouseDragged() etc) + * these are different because mouse events are queued to the end of + * draw, so the previous position has to be updated on each event, + * as opposed to the pmouseX/Y that's used inside draw, which is expected + * to be updated once per trip through draw(). + */ + protected int emouseX, emouseY; + + /** + * Used to set pmouseX/Y to mouseX/Y the first time mouseX/Y are used, + * otherwise pmouseX/Y are always zero, causing a nasty jump. + *

+ * Just using (frameCount == 0) won't work since mouseXxxxx() + * may not be called until a couple frames into things. + */ + public boolean firstMouse; + + /** + * Last mouse button pressed, one of LEFT, CENTER, or RIGHT. + *

+ * If running on Mac OS, a ctrl-click will be interpreted as + * the righthand mouse button (unlike Java, which reports it as + * the left mouse). + */ + public int mouseButton; + + public boolean mousePressed; + public MouseEvent mouseEvent; + + /** + * Last key pressed. + *

+ * If it's a coded key, i.e. UP/DOWN/CTRL/SHIFT/ALT, + * this will be set to CODED (0xffff or 65535). + */ + public char key; + + /** + * When "key" is set to CODED, this will contain a Java key code. + *

+ * For the arrow keys, keyCode will be one of UP, DOWN, LEFT and RIGHT. + * Also available are ALT, CONTROL and SHIFT. A full set of constants + * can be obtained from java.awt.event.KeyEvent, from the VK_XXXX variables. + */ + public int keyCode; + + /** + * true if the mouse is currently pressed. + */ + public boolean keyPressed; + + /** + * the last KeyEvent object passed into a mouse function. + */ + public KeyEvent keyEvent; + + /** + * Gets set to true/false as the applet gains/loses focus. + */ + public boolean focused = false; + + /** + * true if the applet is online. + *

+ * This can be used to test how the applet should behave + * since online situations are different (no file writing, etc). + */ + public boolean online = false; + + /** + * Time in milliseconds when the applet was started. + *

+ * Used by the millis() function. + */ + long millisOffset; + + /** + * The current value of frames per second. + *

+ * The initial value will be 10 fps, and will be updated with each + * frame thereafter. The value is not instantaneous (since that + * wouldn't be very useful since it would jump around so much), + * but is instead averaged (integrated) over several frames. + * As such, this value won't be valid until after 5-10 frames. + */ + public float frameRate = 10; + protected long frameRateLastMillis = 0; + + /** Last time in milliseconds that a frameRate delay occurred */ + protected long frameRateLastDelayTime = 0; + /** As of release 0116, frameRate(60) is called as a default */ + protected float frameRateTarget = 60; + + protected boolean looping; + + /** flag set to true when a redraw is asked for by the user */ + protected boolean redraw; + + /** + * How many frames have been displayed since the applet started. + *

+ * This value is read-only do not attempt to set it, + * otherwise bad things will happen. + *

+ * Inside setup(), frameCount is 0. + * For the first iteration of draw(), frameCount will equal 1. + */ + public int frameCount; + + /** + * true if this applet has had it. + */ + public boolean finished; + + /** + * true if exit() has been called so that things shut down + * once the main thread kicks off. + */ + protected boolean exit; + + Thread thread; + + /** + * Set to the an exception that occurs inside run() and is not + * caught.

Used by PdeRuntime to determine what happened and + * report back to the user. + */ + public Exception exception; + //public Throwable exception; + + protected RegisteredMethods sizeMethods; + protected RegisteredMethods preMethods, drawMethods, postMethods; + protected RegisteredMethods mouseEventMethods, keyEventMethods; + protected RegisteredMethods disposeMethods; + + // this text isn't seen unless PApplet is used on its + // own and someone takes advantage of leechErr.. not likely + static public final String LEECH_WAKEUP = "Error while running applet."; + public PrintStream leechErr; + + // messages to send if attached as an external vm + + /** + * Position of the upper-lefthand corner of the editor window + * that launched this applet. + */ + static public final String ARGS_EDITOR_LOCATION = "--editor-location"; + + /** + * Location for where to position the applet window on screen. + *

+ * This is used by the editor to when saving the previous applet + * location, or could be used by other classes to launch at a + * specific position on-screen. + */ + static public final String ARGS_EXTERNAL = "--external"; + + static public final String ARGS_LOCATION = "--location"; + + static public final String ARGS_DISPLAY = "--display"; + + static public final String ARGS_BGCOLOR = "--bgcolor"; + + static public final String ARGS_PRESENT = "--present"; + + static public final String ARGS_STOP_COLOR = "--stop-color"; + + static public final String ARGS_HIDE_STOP = "--hide-stop"; + + /** + * Allows the user or PdeEditor to set a specific sketch folder path. + *

+ * Used by PdeEditor to pass in the location where saveFrame() + * and all that stuff should write things. + */ + static public final String ARGS_SKETCH_FOLDER = "--sketch-path"; + + /** + * Message from parent editor (when run as external) to quit. + */ + static public final char EXTERNAL_STOP = 's'; + + /** + * When run externally to a PdeEditor, + * this is sent by the applet when it quits. + */ + static public final String EXTERNAL_QUIT = "__QUIT__"; + + /** + * When run externally to a PdeEditor, this is sent by the applet + * whenever the window is moved. + *

+ * This is used so that the editor can re-open the sketch window + * in the same position as the user last left it. + */ + static public final String EXTERNAL_MOVE = "__MOVE__"; + + + // during rev 0100 dev cycle, working on new threading model, + // but need to disable and go conservative with changes in order + // to get pdf and audio working properly first. + // for 0116, the CRUSTY_THREADS are being disabled to fix lots of bugs. + static final boolean CRUSTY_THREADS = false; //true; + + + public void init() { + // first get placed size in case it's non-zero + Dimension initialSize = getSize(); + + // send tab keys through to the PApplet + try { + if (javaVersion >= 1.4f) { + //setFocusTraversalKeysEnabled(false); // 1.4-only function + Method defocus = + Component.class.getMethod("setFocusTraversalKeysEnabled", + new Class[] { Boolean.TYPE }); + defocus.invoke(this, new Object[] { Boolean.FALSE }); + } + } catch (Exception e) { } // oh well + + millisOffset = System.currentTimeMillis(); + + finished = false; // just for clarity + + // this will be cleared by draw() if it is not overridden + looping = true; + redraw = true; // draw this guy once + firstMouse = true; + + // these need to be inited before setup + sizeMethods = new RegisteredMethods(); + preMethods = new RegisteredMethods(); + drawMethods = new RegisteredMethods(); + postMethods = new RegisteredMethods(); + mouseEventMethods = new RegisteredMethods(); + keyEventMethods = new RegisteredMethods(); + disposeMethods = new RegisteredMethods(); + + try { + getAppletContext(); + online = true; + } catch (NullPointerException e) { + online = false; + } + + if (javaVersion < 1.3f) { + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + link("http://java.com/"); + } + }); + // no init to do, so don't cause no trouble, boy + return; + // call this after making the methods to minimize the + // number of places needing the javaVersion crap + // (also needs to check online first and create empty + // stop method register list) + } + + try { + if (sketchPath == null) { + sketchPath = System.getProperty("user.dir"); + } + } catch (Exception e) { } // may be a security problem + + // create a dummy graphics context + if ((initialSize.width != 0) && (initialSize.height != 0)) { + size(initialSize.width, initialSize.height); + } else { + //System.out.println("setting default"); + size(DEFAULT_WIDTH, DEFAULT_HEIGHT); + this.defaultSize = true; + //System.out.println("zeroing"); + //this.width = 0; // use this to flag whether the width/height are valid + //this.height = 0; + // need to set width/height otherwise + // they won't work for static mode apps + //defaultSize = true; + } + + // this is automatically called in applets + // though it's here for applications anyway + start(); + } + + + /** + * Called by the browser or applet viewer to inform this applet that it + * should start its execution. It is called after the init method and + * each time the applet is revisited in a Web page. + *

+ * Called explicitly via the first call to PApplet.paint(), because + * PAppletGL needs to have a usable screen before getting things rolling. + */ + public void start() { + if (javaVersion < 1.3f) return; + + if (thread != null) return; + thread = new Thread(this); + thread.start(); + } + + + /** + * Called by the browser or applet viewer to inform + * this applet that it should stop its execution. + *

+ * Unfortunately, there are no guarantees from the Java spec + * when or if stop() will be called (i.e. on browser quit, + * or when moving between web pages), and it's not always called. + */ + public void stop() { + // maybe start should also be used as the method for kicking + // the thread on, instead of doing it inside paint() + + // bringing this back for 0111, hoping it'll help opengl shutdown + finished = true; // why did i comment this out? + + //System.out.println("stopping applet " + thread); + + // don't run stop and disposers twice + if (thread == null) return; + thread = null; + + // call to shut down renderer, in case it needs it (pdf does) + if (g != null) g.dispose(); + + // maybe this should be done earlier? might help ensure it gets called + // before the vm just craps out since 1.5 craps out so aggressively. + disposeMethods.handle(); + } + + + /** + * Called by the browser or applet viewer to inform this applet + * that it is being reclaimed and that it should destroy + * any resources that it has allocated. + *

+ * This also attempts to call PApplet.stop(), in case there + * was an inadvertent override of the stop() function by a user. + *

+ * destroy() supposedly gets called as the applet viewer + * is shutting down the applet. stop() is called + * first, and then destroy() to really get rid of things. + * no guarantees on when they're run (on browser quit, or + * when moving between pages), though. + */ + public void destroy() { + ((PApplet)this).stop(); + } + + + /** + * This returns the last width and height specified by the user + * via the size() command. + */ + public Dimension getPreferredSize() { + return new Dimension(width, height); + } + + + ////////////////////////////////////////////////////////////// + + + public class RegisteredMethods { + int count; + Object objects[]; + Method methods[]; + + + // convenience version for no args + public void handle() { + handle(new Object[] { }); + } + + public void handle(Object oargs[]) { + for (int i = 0; i < count; i++) { + try { + //System.out.println(objects[i] + " " + args); + methods[i].invoke(objects[i], oargs); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void add(Object object, Method method) { + if (objects == null) { + objects = new Object[5]; + methods = new Method[5]; + } + if (count == objects.length) { + Object otemp[] = new Object[count << 1]; + System.arraycopy(objects, 0, otemp, 0, count); + objects = otemp; + Method mtemp[] = new Method[count << 1]; + System.arraycopy(methods, 0, mtemp, 0, count); + methods = mtemp; + } + objects[count] = object; + methods[count] = method; + count++; + } + } + + + public void registerSize(Object o) { + Class methodArgs[] = new Class[] { Integer.TYPE, Integer.TYPE }; + registerWithArgs(sizeMethods, "size", o, methodArgs); + } + + public void registerPre(Object o) { + registerNoArgs(preMethods, "pre", o); + } + + public void registerDraw(Object o) { + registerNoArgs(drawMethods, "draw", o); + } + + public void registerPost(Object o) { + registerNoArgs(postMethods, "post", o); + } + + public void registerMouseEvent(Object o) { + Class methodArgs[] = new Class[] { MouseEvent.class }; + registerWithArgs(mouseEventMethods, "mouseEvent", o, methodArgs); + } + + + public void registerKeyEvent(Object o) { + Class methodArgs[] = new Class[] { KeyEvent.class }; + registerWithArgs(keyEventMethods, "keyEvent", o, methodArgs); + } + + public void registerDispose(Object o) { + registerNoArgs(disposeMethods, "dispose", o); + } + + + protected void registerNoArgs(RegisteredMethods meth, + String name, Object o) { + Class c = o.getClass(); + try { + Method method = c.getMethod(name, new Class[] {}); + meth.add(o, method); + + } catch (Exception e) { + die("Could not register " + name + " + () for " + o, e); + } + } + + + protected void registerWithArgs(RegisteredMethods meth, + String name, Object o, Class cargs[]) { + Class c = o.getClass(); + try { + Method method = c.getMethod(name, cargs); + meth.add(o, method); + + } catch (Exception e) { + die("Could not register " + name + " + () for " + o, e); + } + } + + + ////////////////////////////////////////////////////////////// + + + public void setup() { + } + + + public void draw() { + // if no draw method, then shut things down + //System.out.println("no draw method, goodbye"); + finished = true; + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Starts up and creates a two-dimensional drawing surface, + * or resizes the current drawing surface. + *

+ * This should be the first thing called inside of setup(). + *

+ * If using Java 1.3 or later, this will default to using + * PGraphics2, the Java2D-based renderer. If using Java 1.1, + * or if PGraphics2 is not available, then PGraphics will be used. + * To set your own renderer, use the other version of the size() + * method that takes a renderer as its last parameter. + *

+ * If called once a renderer has already been set, this will + * use the previous renderer and simply resize it. + */ + public void size(int iwidth, int iheight) { + if (g != null) { + // just resize the current renderer + size(iwidth, iheight, g.getClass().getName()); + + } else { + // create a JAVA2D renderer (the current default) + size(iwidth, iheight, JAVA2D); + + /* + if (PApplet.javaVersion >= 1.3f) { + try { + Class c = Class.forName(JAVA2D); + size(iwidth, iheight, JAVA2D); + return; + + } catch (ClassNotFoundException e) { } + size(iwidth, iheight, P2D); // fall-through case + } + */ + } + } + + + public void size(int iwidth, int iheight, String irenderer) { + size(iwidth, iheight, irenderer, null); + } + + + /** + * Creates a new PGraphics object and sets it to the specified size. + *

+ * Note that you cannot change the renderer once outside of setup(). + * In most cases, you can call size() to give it a new size, + * but you need to always ask for the same renderer, otherwise + * you're gonna run into trouble. + *

+ * XXXX Also note that this calls defaults(), which will reset any + * XXXX settings for the font, stroke, fill, colorMode, lights, etc. + */ + public void size(int iwidth, int iheight, + String irenderer, String ipath) { + String currentRenderer = + (g == null) ? null : g.getClass().getName(); + // ensure that this is an absolute path + if (ipath != null) ipath = savePath(ipath); + + if (currentRenderer != null) { + if (currentRenderer.equals(irenderer)) { + if ((iwidth == g.width) && (iheight == g.height)) { + // in this case, size() is being called a second time because + // setup() is being called a second time, since the first time + // that setup was called, the renderer was changed so an + // exception was thrown and setup() didn't complete. but this + // time around, g is the proper size and the proper class. + + // that or size() is being called again for no good reason, + // in which case we just ignore it anyway. + + // so all that needs to be done is to set the defaults + // (clear the background, set default strokeWeight, etc). + //g.defaults(); + // removed this in favor of calling defaults() from beginDraw() + + // this will happen when P3D or OPENGL are used with size() + // inside of setup. it's also safe to call defaults() now, + // because it's happening inside setup, which is just frame 0, + // meaning that the graphics context is proper and visible. + + } else { // just resizing, no need to create new graphics object + g.resize(iwidth, iheight); + updateSize(iwidth, iheight); + redraw(); // changed for rev 0100 + } + // in either case, the renderer is unchanged, so return + //return; + + } else { // renderer is being changed + if (frameCount > 0) { + throw new RuntimeException("size() cannot be called to change " + + "the renderer outside of setup()"); + } + // otherwise ok to fall through and create renderer below + // the renderer is changing, so need to create a new object + g = PApplet.createGraphics(iwidth, iheight, irenderer, ipath, this); + //g.setMainDrawingSurface(); + //if (g != null) { + updateSize(iwidth, iheight); + //} + + // throw an exception so that setup() is called again + // but with a properly sized render + // this is for opengl, which needs a valid, properly sized + // display before calling anything inside setup(). + throw new RuntimeException(NEW_RENDERER); + } + } else { // none exists, just create a freshy + g = PApplet.createGraphics(iwidth, iheight, irenderer, ipath, this); + //g.setMainDrawingSurface(); + updateSize(iwidth, iheight); + } + + /* + // the renderer is changing, so need to create a new object + g = createGraphics(iwidth, iheight, irenderer); + //if (g != null) { + updateSize(iwidth, iheight); + //} + + //if ((currentRenderer != null) && + // !currentRenderer.equals(irenderer)) { + if (currentRenderer != null) { + // throw an exception so that setup() is called again + // but with a properly sized render + // this is for opengl, which needs a valid, properly sized + // display before calling anything inside setup(). + throw new RuntimeException(NEW_RENDERER); + } + */ + } + + + /** + * Sets this.width and this.height, unsets defaultSize, and calls + * the size() methods inside any libraries. + */ + protected void updateSize(int iwidth, int iheight) { + this.width = iwidth; + this.height = iheight; + defaultSize = false; + + // make the applet itself larger.. it's a subclass of Component, + // so this is important for when it's embedded inside another app. + setSize(width, height); + + // probably needs to mess with the parent frame here? + // TODO wait for a "legitimate size" flag to be set + // (meaning that setup has finished properly) + // at which time the parent frame will do its thing. + + // if the default renderer is just being resized, + // restore it to its default values + //g.defaults(); + // no, otherwise fonts that were set in setup() will go away + + // this has to be called after the exception is thrown, + // otherwise the supporting libs won't have a valid context to draw to + Object methodArgs[] = + new Object[] { new Integer(width), new Integer(height) }; + sizeMethods.handle(methodArgs); + } + + + /** + * Create an offscreen PGraphics object for drawing. This can be used + * for bitmap or vector images drawing or rendering. + * + *

+ */ + public PGraphics createGraphics(int iwidth, int iheight, + String irenderer) { + PGraphics pg = + PApplet.createGraphics(iwidth, iheight, irenderer, null, null); + pg.parent = this; // make save() work + return pg; + } + + + /** + * Create an offscreen graphics surface for drawing, in this case + * for a renderer that writes to a file (such as PDF or DXF). + * @param ipath can be an absolute or relative path + */ + public PGraphics createGraphics(int iwidth, int iheight, + String irenderer, String ipath) { + if (ipath != null) { + ipath = savePath(ipath); + } + PGraphics pg = + PApplet.createGraphics(iwidth, iheight, irenderer, ipath, null); + pg.parent = this; // make save() work + return pg; + } + + + /** + * Version of createGraphics() used internally. + * + * @param ipath must be an absolute path, usually set via savePath() + * @oaram applet the parent applet object, this should only be non-null + * in cases where this is the main drawing surface object. + */ + static protected PGraphics createGraphics(int iwidth, int iheight, + String irenderer, String ipath, + PApplet applet) { + if (irenderer.equals(OPENGL)) { + if (PApplet.platform == WINDOWS) { + String s = System.getProperty("java.version"); + if (s != null) { + if (s.equals("1.5.0_10")) { + System.err.println("OpenGL support is broken with Java 1.5.0_10"); + System.err.println("See http://dev.processing.org" + + "/bugs/show_bug.cgi?id=513 for more info."); + throw new RuntimeException("Please update your Java " + + "installation (see bug #513)"); + } + } + } + } + + /* + if (irenderer.equals(P2D)) { + throw new RuntimeException("P2D is not yet implemented, " + + "use JAVA2D or P3D instead."); + } + */ + + /* + // ok when calling size, but not really with createGraphics() + if (renderer.equals(OPENGL)) { + throw new RuntimeException("createGraphics() with OPENGL is not " + + "supported. Use P3D instead."); + } + */ + + String openglError = + "Before using OpenGL, first select " + + "Import Library > opengl from the Sketch menu."; + + try { + Class rendererClass = Class.forName(irenderer); + Class constructorParams[] = null; + Object constructorValues[] = null; + + if (ipath == null) { + constructorParams = new Class[] { + Integer.TYPE, Integer.TYPE, PApplet.class + }; + constructorValues = new Object[] { + new Integer(iwidth), new Integer(iheight), applet + }; + } else { + // first make sure that this in a nice, full, absolute path + //ipath = applet.savePath(ipath); + + constructorParams = new Class[] { + Integer.TYPE, Integer.TYPE, PApplet.class, String.class + }; + constructorValues = new Object[] { + new Integer(iwidth), new Integer(iheight), applet, ipath + }; + } + + Constructor constructor = + rendererClass.getConstructor(constructorParams); + PGraphics pg = (PGraphics) constructor.newInstance(constructorValues); + return pg; + + } catch (InvocationTargetException ite) { + String msg = ite.getTargetException().getMessage(); + if ((msg != null) && + (msg.indexOf("no jogl in java.library.path") != -1)) { + throw new RuntimeException(openglError); + + } else { + ite.getTargetException().printStackTrace(); + Throwable target = ite.getTargetException(); + if (platform == MACOSX) target.printStackTrace(System.out); // bug + // neither of these help, or work + //target.printStackTrace(System.err); + //System.err.flush(); + //System.out.println(System.err); // and the object isn't null + throw new RuntimeException(target.getMessage()); + } + + } catch (ClassNotFoundException cnfe) { + if (cnfe.getMessage().indexOf("processing.opengl.PGraphicsGL") != -1) { + throw new RuntimeException(openglError); + } else { + throw new RuntimeException("You need to use \"Import Library\" " + + "to add " + irenderer + " to your sketch."); + } + + } catch (Exception e) { + //System.out.println("ex3"); + if ((e instanceof IllegalArgumentException) || + (e instanceof NoSuchMethodException) || + (e instanceof IllegalAccessException)) { + + String msg = "public " + + irenderer.substring(irenderer.lastIndexOf('.') + 1) + + "(int width, int height, PApplet parent" + + ((ipath == null) ? "" : ", String filename") + + ") does not exist."; + throw new RuntimeException(msg); + + } else { + if (platform == MACOSX) e.printStackTrace(System.out); + throw new RuntimeException(e.getMessage()); + } + } + } + + + /** + * Preferred method of creating new PImage objects, ensures that a + * reference to the parent PApplet is included, which makes save() work + * without needing an absolute path. + */ + public PImage createImage(int wide, int high, int format) { + PImage image = new PImage(wide, high, format); + image.parent = this; // make save() work + return image; + } + + public void update(Graphics screen) { + //System.out.println("PApplet.update()"); + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 4 update() external"); + paint(screen); + } + + + synchronized public void paint(Graphics screen) { + if (javaVersion < 1.3f) { + screen.setColor(new Color(64, 64, 64)); + Dimension size = getSize(); + screen.fillRect(0, 0, size.width, size.height); + screen.setColor(Color.white); + screen.setFont(new Font("Dialog", Font.PLAIN, 9)); + screen.drawString("You need to install", 3, 15); + screen.drawString("Java 1.3 or later", 3, 28); + screen.drawString("to view this content.", 3, 41); + screen.drawString("Click here to visit", 3, 59); + screen.drawString("java.com and install.", 3, 72); + return; + } + + //System.out.println("PApplet.paint()"); + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 5a enter paint"); + + // ignore the very first call to paint, since it's coming + // from the o.s., and the applet will soon update itself anyway. + //if (firstFrame) return; + if (frameCount == 0) { + // paint() may be called more than once before things + // are finally painted to the screen and the thread gets going + //System.out.println("not painting"); + /* + if (thread == null) { + initGraphics(); + start(); + } + */ + return; + } + + // without ignoring the first call, the first several frames + // are confused because paint() gets called in the midst of + // the initial nextFrame() call, so there are multiple + // updates fighting with one another. + + // g.image is synchronized so that draw/loop and paint don't + // try to fight over it. this was causing a randomized slowdown + // that would cut the frameRate into a third on macosx, + // and is probably related to the windows sluggishness bug too + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 5b enter paint sync"); + + //synchronized (g) { + //synchronized (glock) { + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 5c inside paint sync"); + //System.out.println("5b paint has sync"); + //Exception e = new Exception(); + //e.printStackTrace(); + + // moving this into PGraphics caused weird sluggishness on win2k + //g.mis.newPixels(pixels, g.cm, 0, width); // must call this + + // make sure the screen is visible and usable + // (also prevents over-drawing when using PGraphicsGL) + if ((g != null) && (g.image != null)) { + screen.drawImage(g.image, 0, 0, null); + } + //if (THREAD_DEBUG) println("notifying all"); + //notifyAll(); + //thread.notify(); + //System.out.println(" 6 exit paint"); + //} + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 6 exit paint"); + //updated = true; + } + + + synchronized public void handleDisplay() { + if (PApplet.THREAD_DEBUG) println(Thread.currentThread().getName() + + " formerly nextFrame()"); + if (looping || redraw) { + /* + if (frameCount == 0) { // needed here for the sync + //createGraphics(); + // set up a dummy graphics in case size() is never + // called inside setup + size(INITIAL_WIDTH, INITIAL_HEIGHT); + } + */ + + // g may be rebuilt inside here, so turning of the sync + //synchronized (g) { + // use a different sync object + //synchronized (glock) { + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 1a beginDraw"); + g.beginDraw(); + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 1b draw"); + + //boolean recorderNull = true; + //boolean recorderRawNull = true; + + if (frameCount == 0) { + try { + //System.out.println("attempting setup"); + //System.out.println("into try"); + setup(); + //g.defaults(); + + //System.out.println("done attempting setup"); + //System.out.println("out of try"); + //g.postSetup(); // FIXME + + } catch (RuntimeException e) { + //System.out.println("runtime extends " + e); + //System.out.println("catching a cold " + e.getMessage()); + String msg = e.getMessage(); + if ((msg != null) && + (e.getMessage().indexOf(NEW_RENDERER) != -1)) { + //System.out.println("got new renderer"); + return; + //continue; // will this work? + + } else { + //e.printStackTrace(System.out); + //System.out.println("re-throwing"); + throw e; + } + } + // if depth() is called inside setup, pixels/width/height + // will be ok by the time it's back out again + + //this.pixels = g.pixels; // make em call loadPixels + // now for certain that we've got a valid size + this.width = g.width; + this.height = g.height; + this.defaultSize = false; + + } else { // frameCount > 0, meaning an actual draw() + // update the current frameRate + if (frameRateLastMillis != 0) { + float elapsed = (float) + (System.currentTimeMillis() - frameRateLastMillis); + if (elapsed != 0) { + frameRate = + (frameRate * 0.9f) + ((1.0f / (elapsed / 1000.0f)) * 0.1f); + } + } + frameRateLastMillis = System.currentTimeMillis(); + + /* + if (frameRateTarget != 0) { + //System.out.println("delaying"); + if (frameRateLastDelayTime == 0) { + frameRateLastDelayTime = System.currentTimeMillis(); + + } else { + long timeToLeave = + frameRateLastDelayTime + (long)(1000.0f / frameRateTarget); + long now = System.currentTimeMillis(); + int napTime = (int) (timeToLeave - now); + if (napTime > 0) { + frameRateLastDelayTime = timeToLeave; + delay(napTime); + } else { + // nap time is negative, need to reset clock (bug #336) + frameRateLastDelayTime = now; + } + } + } + */ + + preMethods.handle(); + + pmouseX = dmouseX; + pmouseY = dmouseY; + + //synchronized (glock) { + //synchronized (this) { + //try { + draw(); + /* + // seems to catch, but then blanks out + } catch (Exception e) { + if (e instanceof InvocationTargetException) { + System.out.println("found poo"); + ((InvocationTargetException)e).getTargetException().printStackTrace(System.out); + } + } + */ + //} + //} + + // set a flag regarding whether the recorders were non-null + // as of draw().. this will prevent the recorder from being + // reset if recordShape() is called in an event method, such + // as mousePressed() + //recorderNull = (recorder == null); + //recorderRawNull = (g.recorderRaw == null); + + // dmouseX/Y is updated only once per frame + dmouseX = mouseX; + dmouseY = mouseY; + + // these are called *after* loop so that valid + // drawing commands can be run inside them. it can't + // be before, since a call to background() would wipe + // out anything that had been drawn so far. + dequeueMouseEvents(); + + dequeueKeyEvents(); + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 2b endDraw"); + + drawMethods.handle(); + //for (int i = 0; i < libraryCount; i++) { + //if (libraryCalls[i][PLibrary.DRAW]) libraries[i].draw(); + //} + + redraw = false; // unset 'redraw' flag in case it was set + // (only do this once draw() has run, not just setup()) + } + + g.endDraw(); + /* + if (!recorderNull) { + if (recorder != null) { + recorder.endDraw(); + recorder = null; + } + } + if (!recorderRawNull) { + if (g.recorderRaw != null) { + g.recorderRaw.endDraw(); + g.recorderRaw = null; + } + } + */ + + //} // older end sync + + //update(); + // formerly 'update' + //if (firstFrame) firstFrame = false; + // internal frame counter + frameCount++; + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 3a calling repaint() " + frameCount); + repaint(); + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 3b calling Toolkit.sync " + frameCount); + getToolkit().sync(); // force repaint now (proper method) + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " 3c done " + frameCount); + //if (THREAD_DEBUG) println(" 3d waiting"); + //wait(); + //if (THREAD_DEBUG) println(" 3d out of wait"); + //frameCount++; + + postMethods.handle(); + //for (int i = 0; i < libraryCount; i++) { + //if (libraryCalls[i][PLibrary.POST]) libraries[i].post(); + //} + //} // end of synchronize + } + } + + + ////////////////////////////////////////////////////////////// + + + public void run() { // not good to make this synchronized, locks things up + try { + while ((Thread.currentThread() == thread) && !finished) { + // render a single frame + g.requestDisplay(this); + + // wait for update & paint to happen before drawing next frame + // this is necessary since the drawing is sometimes in a + // separate thread, meaning that the next frame will start + // before the update/paint is completed + + try { + // Windows doesn't like Thread.yield(), acts as though it hasn't + // even been called and starves the CPU anyway. So have to sleep + // (or wait) at least for some small amount of time (below). + //Thread.yield(); + + // Can't remember when/why I changed the generic nap time to '1' + // (rather than 3 or 5, like back in the day), but I have a feeling + // that some platforms aren't gonna like that. + + // If !looping, sleeps for a nice long time, or until an + // interrupt or notify from a call to loop/noLoop/redraw + + int nap = (looping || finished) ? 1 : 10000; + + // don't nap after setup, because if noLoop() is called this + // will make the first draw wait 10 seconds before showing up + if (frameCount == 1) { + nap = 1; + + } else if (finished) { + nap = 0; + + } else if (looping) { + if (frameRateTarget != 0) { + if (frameRateLastDelayTime == 0) { + frameRateLastDelayTime = System.currentTimeMillis(); + + } else { + long timeToLeave = + frameRateLastDelayTime + (long)(1000.0f / frameRateTarget); + long now = System.currentTimeMillis(); + nap = (int) (timeToLeave - now); + if (nap > 0) { + frameRateLastDelayTime = timeToLeave; + //delay(napTime); + //nap = napTime; + + } else { + // nap time is negative, need to reset clock (bug #336) + frameRateLastDelayTime = now; + } + } + } else { + nap = 1; + } + } + + if (CRUSTY_THREADS) { + Thread.sleep(nap); + } else { + synchronized (blocker) { + if (nap > 0) blocker.wait(nap); + } + } + } catch (InterruptedException e) { } + } + + } catch (Exception e) { + // note that this will not catch errors inside setup() + // those are caught by the PdeRuntime + + //System.out.println("exception occurred (if you don't see a stack " + + // "trace below this message, we've got a bug)"); + finished = true; + if (e instanceof InvocationTargetException) { + //System.out.println("target problem"); + e = (Exception) (((InvocationTargetException) e).getTargetException()); + } + exception = e; + //e.printStackTrace(System.out); + + if (leechErr != null) { + // if draw() mode, make sure that ui stops waiting + // and the run button quits out + leechErr.println(LEECH_WAKEUP); + e.printStackTrace(leechErr); + e.printStackTrace(System.out); + + } else { + System.err.println(LEECH_WAKEUP); + e.printStackTrace(); + e.printStackTrace(System.out); + } + } + if (THREAD_DEBUG) println(Thread.currentThread().getName() + + " thread finished"); + + // this may not be safe? this will get triggered with exit() + // but need to see if this is it + //if ((leechErr == null) && !online) { + //System.exit(0); + //} + + //System.out.println("exiting run " + finished); + stop(); // call to shutdown libs? + + if (exit) { // user called exit() function + if ((leechErr == null) && !online) { + // don't want to call System.exit() when an applet, + // or running inside the PDE (would kill the PDE) + System.exit(0); + } + } + } + + + synchronized public void redraw() { + if (!looping) { + redraw = true; + if (thread != null) { + // wake from sleep (necessary otherwise it'll be + // up to 10 seconds before update) + if (CRUSTY_THREADS) { + thread.interrupt(); + } else { + synchronized (blocker) { + blocker.notifyAll(); + } + } + } + } + } + + + synchronized public void loop() { + if (!looping) { + looping = true; + if (thread != null) { + // wake from sleep (necessary otherwise it'll be + // up to 10 seconds before update) + if (CRUSTY_THREADS) { + thread.interrupt(); + } else { + synchronized (blocker) { + blocker.notifyAll(); + } + } + } + } + } + + + synchronized public void noLoop() { + if (looping) { + looping = false; + + // reset frameRate delay times + frameRateLastDelayTime = 0; + frameRateLastMillis = 0; + + if (thread != null) { + if (CRUSTY_THREADS) { + thread.interrupt(); // wake from sleep + } else { + synchronized (blocker) { + blocker.notifyAll(); + } + /* + try { + wait(); // until a notify + } catch (InterruptedException e) { } + */ + } + } + } + } + + + ////////////////////////////////////////////////////////////// + + + protected boolean listenersAdded; + + public void addListeners() { + if (!listenersAdded) { + addMouseListener(this); + addMouseMotionListener(this); + addKeyListener(this); + addFocusListener(this); + + addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + Component c = e.getComponent(); + Rectangle bounds = c.getBounds(); + //System.out.println("componentResized()"); + //System.out.println(" " + c.getClass().getName()); + //println(" visible " + isVisible()); + //System.out.println(" " + e); + //System.out.println(" bounds: " + bounds); + //int newWidth = bounds.width - bounds.x * 2; + //int newHeight = bounds.height - (bounds.y + bounds.x); + //System.out.println(" new: " + newWidth + " " + newHeight); + + size(bounds.width, bounds.height); + + //if (c == PApplet.this) { + //Container con = (Container) c; + //Dimension newSize = getSize(); + //System.out.println("resizing to " + newSize + " "); + //System.out.println(c.getBounds()); + //System.out.println(e); + //System.out.println(c); + //System.out.println("insets " + con.getInsets()); + //size(newSize.width, newSize.height); + //} + } + }); + + listenersAdded = true; + } + } + + + ////////////////////////////////////////////////////////////// + + + MouseEvent mouseEventQueue[] = new MouseEvent[10]; + int mouseEventCount; + + protected void enqueueMouseEvent(MouseEvent e) { + synchronized (mouseEventQueue) { + if (mouseEventCount == mouseEventQueue.length) { + MouseEvent temp[] = new MouseEvent[mouseEventCount << 1]; + System.arraycopy(mouseEventQueue, 0, temp, 0, mouseEventCount); + mouseEventQueue = temp; + } + mouseEventQueue[mouseEventCount++] = e; + } + } + + protected void dequeueMouseEvents() { + synchronized (mouseEventQueue) { + for (int i = 0; i < mouseEventCount; i++) { + mouseEvent = mouseEventQueue[i]; + handleMouseEvent(mouseEvent); + } + mouseEventCount = 0; + } + } + + + /** + * Actually take action based on a mouse event. + * Internally updates mouseX, mouseY, mousePressed, and mouseEvent. + * Then it calls the event type with no params, + * i.e. mousePressed() or mouseReleased() that the user may have + * overloaded to do something more useful. + */ + protected void handleMouseEvent(MouseEvent event) { + int id = event.getID(); + + // http://dev.processing.org/bugs/show_bug.cgi?id=170 + // also prevents mouseExited() on the mac from hosing the mouse + // position, because x/y are bizarre values on the exit event. + // see also the id check below.. both of these go together + if ((id == MouseEvent.MOUSE_DRAGGED) || + (id == MouseEvent.MOUSE_MOVED)) { + pmouseX = emouseX; + pmouseY = emouseY; + mouseX = event.getX(); + mouseY = event.getY(); + } + + mouseEvent = event; + + int modifiers = event.getModifiers(); + if ((modifiers & InputEvent.BUTTON1_MASK) != 0) { + mouseButton = LEFT; + } else if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { + mouseButton = CENTER; + } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) { + mouseButton = RIGHT; + } + // if running on macos, allow ctrl-click as right mouse + if ((platform == MACOSX) || (platform == MACOS9)) { + if (mouseEvent.isPopupTrigger()) { + mouseButton = RIGHT; + } + } + + mouseEventMethods.handle(new Object[] { event }); + + // this used to only be called on mouseMoved and mouseDragged + // change it back if people run into trouble + if (firstMouse) { + pmouseX = mouseX; + pmouseY = mouseY; + dmouseX = mouseX; + dmouseY = mouseY; + firstMouse = false; + } + + //println(event); + + switch (id) { + case MouseEvent.MOUSE_PRESSED: + mousePressed = true; + mousePressed(); + break; + case MouseEvent.MOUSE_RELEASED: + mousePressed = false; + mouseReleased(); + break; + case MouseEvent.MOUSE_CLICKED: + mouseClicked(); + break; + case MouseEvent.MOUSE_DRAGGED: + mouseDragged(); + break; + case MouseEvent.MOUSE_MOVED: + mouseMoved(); + break; + } + + if ((id == MouseEvent.MOUSE_DRAGGED) || + (id == MouseEvent.MOUSE_MOVED)) { + emouseX = mouseX; + emouseY = mouseY; + } + } + + + /** + * Figure out how to process a mouse event. When loop() has been + * called, the events will be queued up until drawing is complete. + * If noLoop() has been called, then events will happen immediately. + */ + protected void checkMouseEvent(MouseEvent event) { + if (looping) { + enqueueMouseEvent(event); + } else { + handleMouseEvent(event); + } + } + + + /** + * If you override this or any function that takes a "MouseEvent e" + * without calling its super.mouseXxxx() then mouseX, mouseY, + * mousePressed, and mouseEvent will no longer be set. + */ + public void mousePressed(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseReleased(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseClicked(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseEntered(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseExited(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseDragged(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseMoved(MouseEvent e) { + checkMouseEvent(e); + } + + + /** + * Mouse has been pressed, and should be considered "down" + * until mouseReleased() is called. If you must, use + * int button = mouseEvent.getButton(); + * to figure out which button was clicked. It will be one of: + * MouseEvent.BUTTON1, MouseEvent.BUTTON2, MouseEvent.BUTTON3 + * Note, however, that this is completely inconsistent across + * platforms. + */ + public void mousePressed() { } + + /** + * Mouse button has been released. + */ + public void mouseReleased() { } + + /** + * When the mouse is clicked, mousePressed() will be called, + * then mouseReleased(), then mouseClicked(). Note that + * mousePressed is already false inside of mouseClicked(). + */ + public void mouseClicked() { } + + /** + * Mouse button is pressed and the mouse has been dragged. + */ + public void mouseDragged() { } + + /** + * Mouse button is not pressed but the mouse has changed locations. + */ + public void mouseMoved() { } + + + ////////////////////////////////////////////////////////////// + + + KeyEvent keyEventQueue[] = new KeyEvent[10]; + int keyEventCount; + + protected void enqueueKeyEvent(KeyEvent e) { + synchronized (keyEventQueue) { + if (keyEventCount == keyEventQueue.length) { + KeyEvent temp[] = new KeyEvent[keyEventCount << 1]; + System.arraycopy(keyEventQueue, 0, temp, 0, keyEventCount); + keyEventQueue = temp; + } + keyEventQueue[keyEventCount++] = e; + } + } + + protected void dequeueKeyEvents() { + synchronized (keyEventQueue) { + for (int i = 0; i < keyEventCount; i++) { + keyEvent = keyEventQueue[i]; + handleKeyEvent(keyEvent); + } + keyEventCount = 0; + } + } + + + protected void handleKeyEvent(KeyEvent event) { + keyEvent = event; + key = event.getKeyChar(); + keyCode = event.getKeyCode(); + + keyEventMethods.handle(new Object[] { event }); + /* + for (int i = 0; i < libraryCount; i++) { + if (libraryCalls[i][PLibrary.KEY]) { + libraries[i].key(event); // endNet/endSerial etc + } + } + */ + + switch (event.getID()) { + case KeyEvent.KEY_PRESSED: + keyPressed = true; + keyPressed(); + break; + case KeyEvent.KEY_RELEASED: + keyPressed = false; + keyReleased(); + break; + case KeyEvent.KEY_TYPED: + keyTyped(); + break; + } + + // if someone else wants to intercept the key, they should + // set key to zero (or something besides the ESC). + if ((event.getID() == KeyEvent.KEY_PRESSED) && + (key == KeyEvent.VK_ESCAPE)) { + exit(); + } + } + + + protected void checkKeyEvent(KeyEvent event) { + if (looping) { + enqueueKeyEvent(event); + } else { + handleKeyEvent(event); + } + } + + + /** + * Overriding keyXxxxx(KeyEvent e) functions will cause the 'key', + * 'keyCode', and 'keyEvent' variables to no longer work; + * key events will no longer be queued until the end of draw(); + * and the keyPressed(), keyReleased() and keyTyped() methods + * will no longer be called. + */ + public void keyPressed(KeyEvent e) { checkKeyEvent(e); } + public void keyReleased(KeyEvent e) { checkKeyEvent(e); } + public void keyTyped(KeyEvent e) { checkKeyEvent(e); } + + + /** + * Called each time a single key on the keyboard is pressed. + * Because of how operating systems handle key repeats, holding + * down a key will cause multiple calls to keyPressed(), because + * the OS repeat takes over. + *

+ * Examples for key handling: + * (Tested on Windows XP, please notify if different on other + * platforms, I have a feeling Mac OS and Linux may do otherwise) + *

+   * 1. Pressing 'a' on the keyboard:
+   *    keyPressed  with key == 'a' and keyCode == 'A'
+   *    keyTyped    with key == 'a' and keyCode ==  0
+   *    keyReleased with key == 'a' and keyCode == 'A'
+   *
+   * 2. Pressing 'A' on the keyboard:
+   *    keyPressed  with key == 'A' and keyCode == 'A'
+   *    keyTyped    with key == 'A' and keyCode ==  0
+   *    keyReleased with key == 'A' and keyCode == 'A'
+   *
+   * 3. Pressing 'shift', then 'a' on the keyboard (caps lock is off):
+   *    keyPressed  with key == CODED and keyCode == SHIFT
+   *    keyPressed  with key == 'A'   and keyCode == 'A'
+   *    keyTyped    with key == 'A'   and keyCode == 0
+   *    keyReleased with key == 'A'   and keyCode == 'A'
+   *    keyReleased with key == CODED and keyCode == SHIFT
+   *
+   * 4. Holding down the 'a' key.
+   *    The following will happen several times,
+   *    depending on your machine's "key repeat rate" settings:
+   *    keyPressed  with key == 'a' and keyCode == 'A'
+   *    keyTyped    with key == 'a' and keyCode ==  0
+   *    When you finally let go, you'll get:
+   *    keyReleased with key == 'a' and keyCode == 'A'
+   *
+   * 5. Pressing and releasing the 'shift' key
+   *    keyPressed  with key == CODED and keyCode == SHIFT
+   *    keyReleased with key == CODED and keyCode == SHIFT
+   *    (note there is no keyTyped)
+   *
+   * 6. Pressing the tab key in an applet with Java 1.4 will
+   *    normally do nothing, but PApplet dynamically shuts
+   *    this behavior off if Java 1.4 is in use (tested 1.4.2_05 Windows).
+   *    Java 1.1 (Microsoft VM) passes the TAB key through normally.
+   *    Not tested on other platforms or for 1.3.
+   * 
+ */ + public void keyPressed() { } + + + /** + * See keyPressed(). + */ + public void keyReleased() { } + + + /** + * Only called for "regular" keys like letters, + * see keyPressed() for full documentation. + */ + public void keyTyped() { } + + + ////////////////////////////////////////////////////////////// + + // i am focused man, and i'm not afraid of death. + // and i'm going all out. i circle the vultures in a van + // and i run the block. + + + public void focusGained() { } + + public void focusGained(FocusEvent e) { + focused = true; + focusGained(); + } + + + public void focusLost() { } + + public void focusLost(FocusEvent e) { + focused = false; + focusLost(); + } + + + ////////////////////////////////////////////////////////////// + + // getting the time + + + /** + * Get the number of milliseconds since the applet started. + *

+ * This is a function, rather than a variable, because it may + * change multiple times per frame. + */ + public int millis() { + return (int) (System.currentTimeMillis() - millisOffset); + } + + /** Seconds position of the current time. */ + static public int second() { + return Calendar.getInstance().get(Calendar.SECOND); + } + + /** Minutes position of the current time. */ + static public int minute() { + return Calendar.getInstance().get(Calendar.MINUTE); + } + + /** + * Hour position of the current time in international format (0-23). + *

+ * To convert this value to American time:
+ *

int yankeeHour = (hour() % 12);
+   * if (yankeeHour == 0) yankeeHour = 12;
+ */ + static public int hour() { + return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); + } + + /** + * Get the current day of the month (1 through 31). + *

+ * If you're looking for the day of the week (M-F or whatever) + * or day of the year (1..365) then use java's Calendar.get() + */ + static public int day() { + return Calendar.getInstance().get(Calendar.DAY_OF_MONTH); + } + + /** + * Get the current month in range 1 through 12. + */ + static public int month() { + // months are number 0..11 so change to colloquial 1..12 + return Calendar.getInstance().get(Calendar.MONTH) + 1; + } + + /** + * Get the current year. + */ + static public int year() { + return Calendar.getInstance().get(Calendar.YEAR); + } + + + ////////////////////////////////////////////////////////////// + + // controlling time (playing god) + + + /** + * The delay() function causes the program to halt for a specified time. + * Delay times are specified in thousandths of a second. For example, + * running delay(3000) will stop the program for three seconds and + * delay(500) will stop the program for a half-second. Remember: the + * display window is updated only at the end of draw(), so putting more + * than one delay() inside draw() will simply add them together and the new + * frame will be drawn when the total delay is over. + *

+ * I'm not sure if this is even helpful anymore, as the screen isn't + * updated before or after the delay, meaning which means it just + * makes the app lock up temporarily. + */ + public void delay(int napTime) { + if (frameCount == 0) return; + if (napTime > 0) { + try { + if (CRUSTY_THREADS) { + Thread.sleep(napTime); + } else { + wait(napTime); + } + } catch (InterruptedException e) { } + } + } + + + /** + * Set a target frameRate. This will cause delay() to be called + * after each frame so that the sketch synchronizes to a particular speed. + * Note that this only sets the maximum frame rate, it cannot be used to + * make a slow sketch go faster. Sketches have no default frame rate + * setting, and will attempt to use maximum processor power to achieve + * maximum speed. + */ + public void frameRate(float newRateTarget) { + this.frameRateTarget = newRateTarget; + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Get a param from the web page, or (eventually) + * from a properties file. + */ + public String param(String what) { + if (online) { + return getParameter(what); + + } else { + System.err.println("param() only works inside a web browser"); + } + return null; + } + + + /** + * Show status in the status bar of a web browser, or in the + * System.out console. Eventually this might show status in the + * p5 environment itself, rather than relying on the console. + */ + public void status(String what) { + if (online) { + showStatus(what); + + } else { + System.out.println(what); // something more interesting? + } + } + + + public void link(String here) { + link(here, null); + } + + + /** + * Link to an external page without all the muss. + *

+ * When run with an applet, uses the browser to open the url, + * for applications, attempts to launch a browser with the url. + *

+ * Works on Mac OS X and Windows. For Linux, use: + *

open(new String[] { "firefox", url });
+ * or whatever you want as your browser, since Linux doesn't + * yet have a standard method for launching URLs. + */ + public void link(String url, String frameTitle) { + if (online) { + try { + if (frameTitle == null) { + getAppletContext().showDocument(new URL(url)); + } else { + getAppletContext().showDocument(new URL(url), frameTitle); + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Could not open " + url); + } + } else { + try { + if (platform == WINDOWS) { + // the following uses a shell execute to launch the .html file + // note that under cygwin, the .html files have to be chmodded +x + // after they're unpacked from the zip file. i don't know why, + // and don't understand what this does in terms of windows + // permissions. without the chmod, the command prompt says + // "Access is denied" in both cygwin and the "dos" prompt. + //Runtime.getRuntime().exec("cmd /c " + currentDir + "\\reference\\" + + // referenceFile + ".html"); + + // replace ampersands with control sequence for DOS. + // solution contributed by toxi on the bugs board. + url = url.replaceAll("&","^&"); + + // open dos prompt, give it 'start' command, which will + // open the url properly. start by itself won't work since + // it appears to need cmd + Runtime.getRuntime().exec("cmd /c start " + url); + + } else if ((platform == MACOSX) || (platform == MACOS9)) { + //com.apple.mrj.MRJFileUtils.openURL(url); + try { + Class mrjFileUtils = Class.forName("com.apple.mrj.MRJFileUtils"); + Method openMethod = + mrjFileUtils.getMethod("openURL", new Class[] { String.class }); + openMethod.invoke(null, new Object[] { url }); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + //throw new RuntimeException("Can't open URLs for this platform"); + // Just pass it off to open() and hope for the best + open(url); + } + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Could not open " + url); + } + } + } + + + /** + * Attempt to open a file using the platform's shell. + */ + public void open(String filename) { + open(new String[] { filename }); + } + + + static String openLauncher; + + /** + * Launch a process using a platforms shell. This version uses an array + * to make it easier to deal with spaces in the individual elements. + * (This avoids the situation of trying to put single or double quotes + * around different bits). + */ + static public Process open(String argv[]) { + String[] params = null; + + if (platform == WINDOWS) { + // just launching the .html file via the shell works + // but make sure to chmod +x the .html files first + // also place quotes around it in case there's a space + // in the user.dir part of the url + params = new String[] { "cmd", "/c" }; + + } else if (platform == MACOSX) { + params = new String[] { "open" }; + + } else if (platform == LINUX) { + if (openLauncher == null) { + // Attempt to use gnome-open + try { + Process p = Runtime.getRuntime().exec(new String[] { "gnome-open" }); + /*int result =*/ p.waitFor(); + // Not installed will throw an IOException (JDK 1.4.2, Ubuntu 7.04) + openLauncher = "gnome-open"; + } catch (Exception e) { } + } + if (openLauncher == null) { + // Attempt with kde-open + try { + Process p = Runtime.getRuntime().exec(new String[] { "kde-open" }); + /*int result =*/ p.waitFor(); + openLauncher = "kde-open"; + } catch (Exception e) { } + } + if (openLauncher == null) { + System.err.println("Could not find gnome-open or kde-open, " + + "the open() command may not work."); + } + if (openLauncher != null) { + params = new String[] { openLauncher }; + } + //} else { // give up and just pass it to Runtime.exec() + //open(new String[] { filename }); + //params = new String[] { filename }; + } + if (params != null) { + // If the 'open', 'gnome-open' or 'cmd' are already included + if (params[0].equals(argv[0])) { + // then don't prepend those params again + return exec(argv); + } else { + params = concat(params, argv); + return exec(params); + } + } else { + return exec(argv); + } + } + + + static public Process exec(String[] argv) { + try { + return Runtime.getRuntime().exec(argv); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Could not open " + join(argv, ' ')); + } + } + + /* + try { + Runtime.getRuntime().exec("cmd /c \"" + filename + "\""); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Could not open " + filename); + } + + try { + return Runtime.getRuntime().exec(argv); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Could not open " + join(argv, ' ')); + } + } + + /* + static protected String findLinuxLauncher() { + if (linuxLauncher == null) { + // Attempt to use gnome-open + try { + Process p = Runtime.getRuntime().exec(new String[] { "gnome-open" }); + int result = p.waitFor(); + // Not installed will throw an IOException (JDK 1.4.2, Ubuntu 7.04) + linuxLauncher = "gnome-open"; + } catch (Exception e) { } + + // Attempt with kde-open + try { + Process p = Runtime.getRuntime().exec(new String[] { "kde-open" }); + int result = p.waitFor(); + linuxLauncher = "kde-open"; + } catch (Exception e) { } + } + if (linuxLauncher == null) { + System.err.println("Could not find gnome-open or kde-open, " + + "the open() command may not work."); + } + return linuxLauncher; + } + */ + + + ////////////////////////////////////////////////////////////// + + + /** + * Function for an applet/application to kill itself and + * display an error. Mostly this is here to be improved later. + */ + public void die(String what) { + stop(); + throw new RuntimeException(what); + /* + if (online) { + System.err.println("i'm dead.. " + what); + + } else { + System.err.println(what); + System.exit(1); + } + */ + } + + + /** + * Same as above but with an exception. Also needs work. + */ + public void die(String what, Exception e) { + if (e != null) e.printStackTrace(); + die(what); + } + + + /** + * Call to safely exit the sketch when finished. For instance, + * to render a single frame, save it, and quit. + */ + public void exit() { + if (thread == null) { + // exit immediately, stop() has already been called, + // meaning that the main thread has long since exited + if ((leechErr == null) && !online) { + // don't want to call System.exit() when an applet, + // or running inside the PDE (would kill the PDE) + System.exit(0); + } + } else { + finished = true; // stop() will be called as the thread exits + //stop(); + exit = true; + } + } + + + ////////////////////////////////////////////////////////////// + + // SCREEN GRABASS + + + /** + * Intercepts any relative paths to make them absolute (relative + * to the sketch folder) before passing to save() in PImage. + * (Changed in 0100) + */ + public void save(String filename) { + g.save(savePath(filename)); + } + + + /** + * Grab an image of what's currently in the drawing area and save it + * as a .tif or .tga file. + *

+ * Best used just before endDraw() at the end of your draw(). + * This can only create .tif or .tga images, so if neither extension + * is specified it defaults to writing a tiff and adds a .tif suffix. + */ + public void saveFrame() { + if (online) { + System.err.println("Can't use saveFrame() when running in a browser."); + return; + } + + //File file = new File(folder, "screen-" + nf(frame, 4) + ".tif"); + //save(savePath("screen-" + nf(frameCount, 4) + ".tif")); + //save("screen-" + nf(frame, 4) + ".tif"); + g.save(savePath("screen-" + nf(frameCount, 4) + ".tif")); + } + + + /** + * Save the current frame as a .tif or .tga image. + *

+ * The String passed in can contain a series of # signs + * that will be replaced with the screengrab number. + *

+   * i.e. saveFrame("blah-####.tif");
+   *      // saves a numbered tiff image, replacing the
+   *      // #### signs with zeros and the frame number 
+ */ + public void saveFrame(String what) { + if (online) { + System.err.println("Can't use saveFrame() when running in a browser."); + return; + } + + g.save(savePath(insertFrame(what))); + /* + int first = what.indexOf('#'); + int last = what.lastIndexOf('#'); + + if (first == -1) { + g.save(savePath(what)); + + } else { + String prefix = what.substring(0, first); + int count = last - first + 1; + String suffix = what.substring(last + 1); + g.save(savePath(prefix + nf(frameCount, count) + suffix)); + } + */ + } + + + + ////////////////////////////////////////////////////////////// + + // CURSOR + + // + + + int cursorType = ARROW; // cursor type + boolean cursorVisible = true; // cursor visibility flag + PImage invisibleCursor; + + + /** + * Set the cursor type + */ + public void cursor(int _cursor_type) { + setCursor(Cursor.getPredefinedCursor(_cursor_type)); + cursorVisible = true; + cursorType = _cursor_type; + } + + + /** + * Replace the cursor with the specified PImage. The x- and y- + * coordinate of the center will be the center of the image. + */ + public void cursor(PImage image) { + cursor(image, image.width/2, image.height/2); + } + + + /** + * Set a custom cursor to an image with a specific hotspot. + * Only works with JDK 1.2 and later. + * Currently seems to be broken on Java 1.4 for Mac OS X + *

+ * Based on code contributed by Amit Pitaru, plus additional + * code to handle Java versions via reflection by Jonathan Feinberg. + */ + public void cursor(PImage image, int hotspotX, int hotspotY) { + if (javaVersion < 1.2f) { + System.err.println("Java 1.2 or higher is required to use cursor()"); + System.err.println("(You're using version " + javaVersionName + ")"); + return; + } + + // don't set this as cursor type, instead use cursor_type + // to save the last cursor used in case cursor() is called + //cursor_type = Cursor.CUSTOM_CURSOR; + Image jimage = + createImage(new MemoryImageSource(image.width, image.height, + image.pixels, 0, image.width)); + + Point hotspot = new Point(hotspotX, hotspotY); + try { + Method mCustomCursor = + Toolkit.class.getMethod("createCustomCursor", + new Class[] { Image.class, + Point.class, + String.class, }); + Cursor cursor = + (Cursor)mCustomCursor.invoke(Toolkit.getDefaultToolkit(), + new Object[] { jimage, + hotspot, + "no cursor" }); + setCursor(cursor); + cursorVisible = true; + + } catch (NoSuchMethodError e) { + System.err.println("cursor() is not available " + + "when using Java " + javaVersionName); + } catch (IndexOutOfBoundsException e) { + System.err.println("cursor() error: the hotspot " + hotspot + + " is out of bounds for the given image."); + } catch (Exception e) { + System.err.println(e); + } + } + + + /** + * Show the cursor after noCursor() was called. + * Notice that the program remembers the last set cursor type + */ + public void cursor() { + // maybe should always set here? seems dangerous, since + // it's likely that java will set the cursor to something + // else on its own, and the applet will be stuck b/c bagel + // thinks that the cursor is set to one particular thing + if (!cursorVisible) { + cursorVisible = true; + setCursor(Cursor.getPredefinedCursor(cursorType)); + } + } + + + /** + * Hide the cursor by creating a transparent image + * and using it as a custom cursor. + */ + public void noCursor() { + if (!cursorVisible) return; // don't hide if already hidden. + + if (invisibleCursor == null) { + invisibleCursor = new PImage(16, 16, ARGB); + } + // was formerly 16x16, but the 0x0 was added by jdf as a fix + // for macosx, which didn't wasn't honoring the invisible cursor + cursor(invisibleCursor, 0, 0); + cursorVisible = false; + } + + + ////////////////////////////////////////////////////////////// + + + static public void print(byte what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(boolean what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(char what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(int what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(float what) { + System.out.print(what); + System.out.flush(); + } + + /* + static public void print(double what) { + System.out.print(what); + System.out.flush(); + } + */ + + static public void print(String what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(Object what) { + if (what == null) { + // special case since this does fuggly things on > 1.1 + System.out.print("null"); + } else { + System.out.println(what.toString()); + } + + /* + String name = what.getClass().getName(); + if (name.charAt(0) == '[') { + switch (name.charAt(1)) { + case '[': + // don't even mess with multi-dimensional arrays (case '[') + // or anything else that's not int, float, boolean, char + System.out.print(what); + System.out.print(' '); + break; + + case 'L': + // print a 1D array of objects as individual elements + Object poo[] = (Object[]) what; + for (int i = 0; i < poo.length; i++) { + System.out.print(poo[i]); + System.out.print(' '); + } + break; + + case 'Z': // boolean + boolean zz[] = (boolean[]) what; + for (int i = 0; i < zz.length; i++) { + System.out.print(zz[i]); + System.out.print(' '); + } + break; + + case 'B': // byte + byte bb[] = (byte[]) what; + for (int i = 0; i < bb.length; i++) { + System.out.print(bb[i]); + System.out.print(' '); + } + break; + + case 'C': // char + char cc[] = (char[]) what; + for (int i = 0; i < cc.length; i++) { + System.out.print(cc[i]); + System.out.print(' '); + } + break; + + case 'I': // int + int ii[] = (int[]) what; + for (int i = 0; i < ii.length; i++) { + System.out.print(ii[i]); + System.out.print(' '); + } + break; + + case 'F': // float + float ff[] = (float[]) what; + for (int i = 0; i < ff.length; i++) { + System.out.print(ff[i]); + System.out.print(' '); + } + break; + + case 'D': // double + double dd[] = (double[]) what; + for (int i = 0; i < dd.length; i++) { + System.out.print(dd[i]); + System.out.print(' '); + } + break; + + default: + System.out.print(what); + } + } else { + System.out.print(what); //.toString()); + } + */ + } + + // + + static public void println() { + System.out.println(); + } + + // + + static public void println(byte what) { + print(what); System.out.println(); + } + + static public void println(boolean what) { + print(what); System.out.println(); + } + + static public void println(char what) { + print(what); System.out.println(); + } + + static public void println(int what) { + print(what); System.out.println(); + } + + static public void println(float what) { + print(what); System.out.println(); + } + + /* + static public void println(double what) { + print(what); System.out.println(); + } + */ + + static public void println(String what) { + print(what); System.out.println(); + } + + static public void println(Object what) { + if (what == null) { + // special case since this does fuggly things on > 1.1 + System.out.println("null"); + + } else { + String name = what.getClass().getName(); + if (name.charAt(0) == '[') { + switch (name.charAt(1)) { + case '[': + // don't even mess with multi-dimensional arrays (case '[') + // or anything else that's not int, float, boolean, char + System.out.println(what); + break; + + case 'L': + // print a 1D array of objects as individual elements + Object poo[] = (Object[]) what; + for (int i = 0; i < poo.length; i++) { + if (poo[i] instanceof String) { + System.out.println("[" + i + "] \"" + poo[i] + "\""); + } else { + System.out.println("[" + i + "] " + poo[i]); + } + } + break; + + case 'Z': // boolean + boolean zz[] = (boolean[]) what; + for (int i = 0; i < zz.length; i++) { + System.out.println("[" + i + "] " + zz[i]); + } + break; + + case 'B': // byte + byte bb[] = (byte[]) what; + for (int i = 0; i < bb.length; i++) { + System.out.println("[" + i + "] " + bb[i]); + } + break; + + case 'C': // char + char cc[] = (char[]) what; + for (int i = 0; i < cc.length; i++) { + System.out.println("[" + i + "] '" + cc[i] + "'"); + } + break; + + case 'I': // int + int ii[] = (int[]) what; + for (int i = 0; i < ii.length; i++) { + System.out.println("[" + i + "] " + ii[i]); + } + break; + + case 'F': // float + float ff[] = (float[]) what; + for (int i = 0; i < ff.length; i++) { + System.out.println("[" + i + "] " + ff[i]); + } + break; + + /* + case 'D': // double + double dd[] = (double[]) what; + for (int i = 0; i < dd.length; i++) { + System.out.println("[" + i + "] " + dd[i]); + } + break; + */ + + default: + System.out.println(what); + } + } else { // not an array + System.out.println(what); + } + } + } + + // + + /* + // not very useful, because it only works for public (and protected?) + // fields of a class, not local variables to methods + public void printvar(String name) { + try { + Field field = getClass().getDeclaredField(name); + println(name + " = " + field.get(this)); + } catch (Exception e) { + e.printStackTrace(); + } + } + */ + + + ////////////////////////////////////////////////////////////// + + // MATH + + // lots of convenience methods for math with floats. + // doubles are overkill for processing applets, and casting + // things all the time is annoying, thus the functions below. + + + static public final float abs(float n) { + return (n < 0) ? -n : n; + } + + static public final int abs(int n) { + return (n < 0) ? -n : n; + } + + static public final float sq(float a) { + return a*a; + } + + static public final float sqrt(float a) { + return (float)Math.sqrt(a); + } + + static public final float log(float a) { + return (float)Math.log(a); + } + + static public final float exp(float a) { + return (float)Math.exp(a); + } + + static public final float pow(float a, float b) { + return (float)Math.pow(a, b); + } + + + static public final int max(int a, int b) { + return (a > b) ? a : b; + } + + static public final float max(float a, float b) { + return (a > b) ? a : b; + } + + + static public final int max(int a, int b, int c) { + return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); + } + + static public final float max(float a, float b, float c) { + return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); + } + + + /** + * Find the maximum value in an array. + * @param list the source array + * @return The maximum value, or 0 if the array is length zero. + */ + static public final int max(int[] list) { + if (list.length == 0) { + return 0; + } + int max = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] > max) max = list[i]; + } + return max; + } + + /** + * Find the maximum value in an array. + * @param list the source array + * @return The maximum value, or Float.NaN if the array is length zero. + */ + static public final float max(float[] list) { + if (list.length == 0) { + return Float.NaN; + } + float max = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] > max) max = list[i]; + } + return max; + } + + + static public final int min(int a, int b) { + return (a < b) ? a : b; + } + + static public final float min(float a, float b) { + return (a < b) ? a : b; + } + + + static public final int min(int a, int b, int c) { + return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); + } + + static public final float min(float a, float b, float c) { + return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); + } + + + /** + * Find the minimum value in an array. + * @param list the source array + * @return The minimum value, or 0 if the array is length zero. + */ + static public final int min(int[] list) { + if (list.length == 0) { + return 0; + } + int min = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] < min) min = list[i]; + } + return min; + } + /** + * Find the minimum value in an array. + * @param list the source array + * @return The minimum value, or Float.NaN if the array is length zero. + */ + static public final float min(float[] list) { + if (list.length == 0) { + return Float.NaN; + } + float min = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] < min) min = list[i]; + } + return min; + } + + + static public final int constrain(int amt, int low, int high) { + return (amt < low) ? low : ((amt > high) ? high : amt); + } + + static public final float constrain(float amt, float low, float high) { + return (amt < low) ? low : ((amt > high) ? high : amt); + } + + + static public final float sin(float angle) { + return (float)Math.sin(angle); + } + + static public final float cos(float angle) { + return (float)Math.cos(angle); + } + + static public final float tan(float angle) { + return (float)Math.tan(angle); + } + + + static public final float asin(float value) { + return (float)Math.asin(value); + } + + static public final float acos(float value) { + return (float)Math.acos(value); + } + + static public final float atan(float value) { + return (float)Math.atan(value); + } + + static public final float atan2(float a, float b) { + return (float)Math.atan2(a, b); + } + + + static public final float degrees(float radians) { + return radians * RAD_TO_DEG; + } + + static public final float radians(float degrees) { + return degrees * DEG_TO_RAD; + } + + + static public final int ceil(float what) { + return (int) Math.ceil(what); + } + + static public final int floor(float what) { + return (int) Math.floor(what); + } + + static public final int round(float what) { + return (int) Math.round(what); + } + + + static public final float mag(float a, float b) { + return (float)Math.sqrt(a*a + b*b); + } + + static public final float mag(float a, float b, float c) { + return (float)Math.sqrt(a*a + b*b + c*c); + } + + + static public final float dist(float x1, float y1, float x2, float y2) { + return sqrt(sq(x2-x1) + sq(y2-y1)); + } + + static public final float dist(float x1, float y1, float z1, + float x2, float y2, float z2) { + return sqrt(sq(x2-x1) + sq(y2-y1) + sq(z2-z1)); + } + + + static public final float lerp(float start, float stop, float amt) { + return start + (stop-start) * amt; + } + + /** + * Normalize a value to exist between 0 and 1 (inclusive). + * Mathematically the opposite of lerp(), figures out what proportion + * a particular value is relative to start and stop coordinates. + */ + static public final float norm(float value, float start, float stop) { + return (value - start) / (stop - start); + } + + /** + * Convenience function to map a variable from one coordinate space + * to another. Equivalent to unlerp() followed by lerp(). + */ + static public final float map(float value, + float istart, float istop, + float ostart, float ostop) { + return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); + } + + + + ////////////////////////////////////////////////////////////// + + // RANDOM NUMBERS + + + Random internalRandom; + + /** + * Return a random number in the range [0, howbig). + *

+ * The number returned will range from zero up to + * (but not including) 'howbig'. + */ + public final float random(float howbig) { + // for some reason (rounding error?) Math.random() * 3 + // can sometimes return '3' (once in ~30 million tries) + // so a check was added to avoid the inclusion of 'howbig' + + // avoid an infinite loop + if (howbig == 0) return 0; + + // internal random number object + if (internalRandom == null) internalRandom = new Random(); + + float value = 0; + do { + //value = (float)Math.random() * howbig; + value = internalRandom.nextFloat() * howbig; + } while (value == howbig); + return value; + } + + + /** + * Return a random number in the range [howsmall, howbig). + *

+ * The number returned will range from 'howsmall' up to + * (but not including 'howbig'. + *

+ * If howsmall is >= howbig, howsmall will be returned, + * meaning that random(5, 5) will return 5 (useful) + * and random(7, 4) will return 7 (not useful.. better idea?) + */ + public final float random(float howsmall, float howbig) { + if (howsmall >= howbig) return howsmall; + float diff = howbig - howsmall; + return random(diff) + howsmall; + } + + + public final void randomSeed(long what) { + // internal random number object + if (internalRandom == null) internalRandom = new Random(); + internalRandom.setSeed(what); + } + + + + ////////////////////////////////////////////////////////////// + + // PERLIN NOISE + + // [toxi 040903] + // octaves and amplitude amount per octave are now user controlled + // via the noiseDetail() function. + + // [toxi 030902] + // cleaned up code and now using bagel's cosine table to speed up + + // [toxi 030901] + // implementation by the german demo group farbrausch + // as used in their demo "art": http://www.farb-rausch.de/fr010src.zip + + static final int PERLIN_YWRAPB = 4; + static final int PERLIN_YWRAP = 1<>= 1; + } + + if (x<0) x=-x; + if (y<0) y=-y; + if (z<0) z=-z; + + int xi=(int)x, yi=(int)y, zi=(int)z; + float xf = (float)(x-xi); + float yf = (float)(y-yi); + float zf = (float)(z-zi); + float rxf, ryf; + + float r=0; + float ampl=0.5f; + + float n1,n2,n3; + + for (int i=0; i=1.0f) { xi++; xf--; } + if (yf>=1.0f) { yi++; yf--; } + if (zf>=1.0f) { zi++; zf--; } + } + return r; + } + + // [toxi 031112] + // now adjusts to the size of the cosLUT used via + // the new variables, defined above + private float noise_fsc(float i) { + // using bagel's cosine table instead + return 0.5f*(1.0f-perlin_cosTable[(int)(i*perlin_PI)%perlin_TWOPI]); + } + + // [toxi 040903] + // make perlin noise quality user controlled to allow + // for different levels of detail. lower values will produce + // smoother results as higher octaves are surpressed + + public void noiseDetail(int lod) { + if (lod>0) perlin_octaves=lod; + } + + public void noiseDetail(int lod, float falloff) { + if (lod>0) perlin_octaves=lod; + if (falloff>0) perlin_amp_falloff=falloff; + } + + public void noiseSeed(long what) { + if (perlinRandom == null) perlinRandom = new Random(); + perlinRandom.setSeed(what); + // force table reset after changing the random number seed [0122] + perlin = null; + } + + + + ////////////////////////////////////////////////////////////// + + // SOUND I/O + + /* + public PSound loadSound(String filename) { + if (PApplet.javaVersion >= 1.3f) { + return new PSound2(this, openStream(filename)); + } + return new PSound(this, openStream(filename)); + } + */ + + + ////////////////////////////////////////////////////////////// + + // IMAGE I/O + + + /* + Hashtable imageTable; + */ + + /** + * Draw an image based on its filename. This is less efficient than + * using loadImage because there's no way to unload it from memory, + * but it's useful for beginners. + */ + /* + public void image(String filename, float x, float y) { + image(tableImage(filename), x, y); + } + */ + + /** + * Draw an image based on its filename. This is less than efficient + * than using loadImage because there's no way to unload it from memory, + * but it's useful for beginners. + */ + /* + public void image(String filename, + float x, float y, float c, float d) { + image(tableImage(filename), x, y, c, d); + } + */ + + /** + * Draw an image based on its filename. This is less than efficient + * than using loadImage because there's no way to unload it from memory, + * but it's useful for beginners. + */ + /* + public void image(String filename, + float a, float b, float c, float d, + int u1, int v1, int u2, int v2) { + image(tableImage(filename), a, b, c, d, u1, v1, u2, v2); + } + */ + + + /** + * Load an image and store it in a table based on its name. + */ + /* + protected PImage tableImage(String filename) { + if (imageTable == null) imageTable = new Hashtable(); + + PImage image = (PImage) imageTable.get(filename); + if (image != null) return image; + + image = loadImage(filename); + return image; + } + */ + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + protected String[] loadImageFormats; + + + /** + * Load an image from the data folder or a local directory. + * Supports .gif (including transparency), .tga, and .jpg images. + * In Java 1.3 or later, .png images are + * + * also supported. + *

+ * Generally, loadImage() should only be used during setup, because + * re-loading images inside draw() is likely to cause a significant + * delay while memory is allocated and the thread blocks while waiting + * for the image to load because loading is not asynchronous. + *

+ * To load several images asynchronously, see more information in the + * FAQ about writing your own threaded image loading method. + *

+ * As of 0096, returns null if no image of that name is found, + * rather than an error. + *

+ * Release 0115 also provides support for reading TIFF and RLE-encoded + * Targa (.tga) files written by Processing via save() and saveFrame(). + * Other TIFF and Targa files will probably not load, use a different + * format (gif, jpg and png are safest bets) when creating images with + * another application to use with Processing. + *

+ * Also in release 0115, more image formats (BMP and others) can + * be read when using Java 1.4 and later. Because many people still + * use Java 1.1 and 1.3, these formats are not recommended for + * work that will be posted on the web. To get a list of possible + * image formats for use with Java 1.4 and later, use the following: + * println(javax.imageio.ImageIO.getReaderFormatNames()) + *

+ * Images are loaded via a byte array that is passed to + * Toolkit.createImage(). Unfortunately, we cannot use Applet.getImage() + * because it takes a URL argument, which would be a pain in the a-- + * to make work consistently for online and local sketches. + * Sometimes this causes problems, resulting in issues like + * Bug 279 + * and + * Bug 305. + * In release 0115, everything was instead run through javax.imageio, + * but that turned out to be very slow, see + * Bug 392. + * As a result, starting with 0116, the following happens: + *

+ * For releases 0116 and later, if you have problems such as those seen + * in Bugs 279 and 305, use Applet.getImage() instead. You'll be stuck + * with the limitations of getImage() (the headache of dealing with + * online/offline use). Set up your own MediaTracker, and pass the resulting + * java.awt.Image to the PImage constructor that takes an AWT image. + * You can also use the loadImageSync() function (added in 0116) that + * takes an AWT image and loads it synchronously inside PApplet. + *
+   * public PImage loadImageAlt(String filename) {
+   *   java.awt.Image img = getImage(getCodeBase(), filename);
+   *   return loadImageSync(img);
+   * }
+   * 
+ * This isn't much fun, but this will have to do unless we find the + * actual culprit, which may still be a threading issue. + */ + public PImage loadImage(String filename) { + String lower = filename.toLowerCase(); + int dot = filename.lastIndexOf('.'); + if (dot == -1) { + // no extension found + return loadImage(filename, "unknown"); + } + String extension = lower.substring(dot + 1); + + // check for, and strip any parameters on the url, i.e. + // filename.jpg?blah=blah&something=that + int question = extension.indexOf('?'); + if (question != -1) { + extension = extension.substring(0, question); + } + + return loadImage(filename, extension); + } + + + /** + * Identical to loadImage, but allows you to specify the type of + * image by its extension. Especially useful when downloading from + * CGI scripts. + *

+ * Use 'unknown' as the extension to pass off to the default + * image loader that handles gif, jpg, and png. + */ + public PImage loadImage(String filename, String extension) { + // just in case. them users will try anything! + extension = extension.toLowerCase(); + + if (extension.equals("tga")) { + try { + return loadImageTGA(filename); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + //if (lower.endsWith(".tif") || lower.endsWith(".tiff")) { + if (extension.equals("tif") || extension.equals("tiff")) { + byte bytes[] = loadBytes(filename); + return (bytes == null) ? null : PImage.loadTIFF(bytes); + } + + // Make sure that PNG images aren't being loaded by Java 1.1 + //if (lower.endsWith(".png") && PApplet.javaVersion < 1.3f) { + if (extension.equals("png") && PApplet.javaVersion < 1.3f) { + System.err.println("PNG images can only be loaded when " + + "using Java 1.3 and later."); + return null; + } + + // For jpeg, gif, and png, load them using createImage(), + // because the javax.imageio code was found to be much slower, see + // Bug 392. + try { + //if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || + // lower.endsWith(".gif") || lower.endsWith(".png")) { + if (extension.equals("jpg") || extension.equals("jpeg") || + extension.equals("gif") || extension.equals("png") || + extension.equals("unknown")) { + byte bytes[] = loadBytes(filename); + if (bytes == null) { + return null; + } else { + Image awtImage = Toolkit.getDefaultToolkit().createImage(bytes); + PImage image = loadImageSync(awtImage); + // if it's a .gif image, test to see if it has transparency + //if ((lower.endsWith(".gif")) || (lower.endsWith(".png"))) { + if (extension.equals("gif") || extension.equals("png")) { + image.checkAlpha(); + } + return image; + } + } + } catch (Exception e) { + // show error, but move on to the stuff below, see if it'll work + e.printStackTrace(); + } + + if (PApplet.javaVersion >= 1.4f) { + if (loadImageFormats == null) { + //loadImageFormats = javax.imageio.ImageIO.getReaderFormatNames(); + try { + Class ioClass = Class.forName("javax.imageio.ImageIO"); + Method getFormatNamesMethod = + ioClass.getMethod("getReaderFormatNames", (Class[]) null); + loadImageFormats = (String[]) + getFormatNamesMethod.invoke((Class[]) null, (Object[]) null); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (loadImageFormats != null) { + for (int i = 0; i < loadImageFormats.length; i++) { + //if (filename.endsWith("." + loadImageFormats[i])) { + if (extension.equals(loadImageFormats[i])) { + return loadImageIO(filename); + } + } + } + } + + // failed, could not load image after all those attempts + System.err.println("Could not find a method to load " + filename); + return null; + } + + + /** + * Load an AWT image synchronously. + */ + public PImage loadImageSync(Image awtImage) { + MediaTracker tracker = new MediaTracker(this); + tracker.addImage(awtImage, 0); + try { + tracker.waitForAll(); + } catch (InterruptedException e) { + //e.printStackTrace(); // non-fatal, right? + } + + PImage image = new PImage(awtImage); + image.parent = this; + return image; + } + + + /** + * Use Java 1.4 ImageIO methods to load an image. All done via reflection + * in order to maintain compatability with previous releases. + */ + protected PImage loadImageIO(String filename) { + InputStream stream = openStream(filename); + if (stream == null) { + System.err.println("The image " + filename + " could not be found."); + return null; + } + + try { + Class ioClass = Class.forName("javax.imageio.ImageIO"); + Method readMethod = + ioClass.getMethod("read", new Class[] { InputStream.class }); + Object bimage = readMethod.invoke(null, new Object[] { stream }); + + // need to get width and height, then create pixels[] at that size + //int px[] = null; + + Class biClass = + Class.forName("java.awt.image.BufferedImage"); + + Method getHeightMethod = + biClass.getMethod("getHeight", (Class[]) null); + Integer hi = (Integer) getHeightMethod.invoke(bimage, (Object[]) null); + + Method getWidthMethod = + biClass.getMethod("getWidth", (Class[]) null); + Integer wi = (Integer) getWidthMethod.invoke(bimage, (Object[]) null); + + // was gonna call getType() on the image to see if RGB or ARGB, + // but it's not actually useful, since gif images will come through + // as TYPE_BYTE_INDEXED, which means it'll still have to check for + // the transparency. also, would have to iterate through all the other + // types and guess whether alpha was in there, so.. just gonna stick + // with the old method. + + PImage outgoing = new PImage(wi.intValue(), hi.intValue()); + outgoing.parent = this; + + Method getRgbMethod = + biClass.getMethod("getRGB", new Class[] { + Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE, + outgoing.pixels.getClass(), Integer.TYPE, Integer.TYPE + }); + getRgbMethod.invoke(bimage, new Object[] { + new Integer(0), new Integer(0), + new Integer(outgoing.width), new Integer(outgoing.height), + outgoing.pixels, new Integer(0), new Integer(outgoing.width) + }); + + // check the alpha for this image + outgoing.checkAlpha(); + + // return the image + return outgoing; + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + /** + * Targa image loader for RLE-compressed TGA files. + *

+ * Rewritten for 0115 to read/write RLE-encoded targa images. + * For 0125, non-RLE encoded images are now supported, along with + * images whose y-order is reversed (which is standard for TGA files). + */ + protected PImage loadImageTGA(String filename) throws IOException { + InputStream is = openStream(filename); + if (is == null) return null; + + byte header[] = new byte[18]; + int offset = 0; + do { + int count = is.read(header, offset, header.length - offset); + if (count == -1) return null; + offset += count; + } while (offset < 18); + + /* + header[2] image type code + 2 (0x02) - Uncompressed, RGB images. + 3 (0x03) - Uncompressed, black and white images. + 10 (0x0A) - Runlength encoded RGB images. + 11 (0x0B) - Compressed, black and white images. (grayscale?) + + header[16] is the bit depth (8, 24, 32) + + header[17] image descriptor (packed bits) + 0x20 is 32 = origin upper-left + 0x28 is 32 + 8 = origin upper-left + 32 bits + + 7 6 5 4 3 2 1 0 + 128 64 32 16 8 4 2 1 + */ + + int format = 0; + + if (((header[2] == 3) || (header[2] == 11)) && // B&W, plus RLE or not + (header[16] == 8) && // 8 bits + ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 bit + format = ALPHA; + + } else if (((header[2] == 2) || (header[2] == 10)) && // RGB, RLE or not + (header[16] == 24) && // 24 bits + ((header[17] == 0x20) || (header[17] == 0))) { // origin + format = RGB; + + } else if (((header[2] == 2) || (header[2] == 10)) && + (header[16] == 32) && + ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 + format = ARGB; + } + + if (format == 0) { + System.err.println("Unknown .tga file format for " + filename); + //" (" + header[2] + " " + + //(header[16] & 0xff) + " " + + //hex(header[17], 2) + ")"); + return null; + } + + int w = ((header[13] & 0xff) << 8) + (header[12] & 0xff); + int h = ((header[15] & 0xff) << 8) + (header[14] & 0xff); + PImage outgoing = createImage(w, h, format); + + boolean reversed = (header[17] & 0x20) != 0; + + if ((header[2] == 2) || (header[2] == 3)) { // not RLE encoded + if (reversed) { + int index = (h-1) * w; + switch (format) { + case ALPHA: + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + outgoing.pixels[index + x] = is.read(); + } + index -= w; + } + break; + case RGB: + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + outgoing.pixels[index + x] = + is.read() | (is.read() << 8) | (is.read() << 16) | + 0xff000000; + } + index -= w; + } + break; + case ARGB: + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + outgoing.pixels[index + x] = + is.read() | (is.read() << 8) | (is.read() << 16) | + (is.read() << 24); + } + index -= w; + } + } + } else { // not reversed + int count = w * h; + switch (format) { + case ALPHA: + for (int i = 0; i < count; i++) { + outgoing.pixels[i] = is.read(); + } + break; + case RGB: + for (int i = 0; i < count; i++) { + outgoing.pixels[i] = + is.read() | (is.read() << 8) | (is.read() << 16) | + 0xff000000; + } + break; + case ARGB: + for (int i = 0; i < count; i++) { + outgoing.pixels[i] = + is.read() | (is.read() << 8) | (is.read() << 16) | + (is.read() << 24); + } + break; + } + } + + } else { // header[2] is 10 or 11 + int index = 0; + int px[] = outgoing.pixels; + + while (index < px.length) { + int num = is.read(); + boolean isRLE = (num & 0x80) != 0; + if (isRLE) { + num -= 127; // (num & 0x7F) + 1 + int pixel = 0; + switch (format) { + case ALPHA: + pixel = is.read(); + break; + case RGB: + pixel = 0xFF000000 | + is.read() | (is.read() << 8) | (is.read() << 16); + //(is.read() << 16) | (is.read() << 8) | is.read(); + break; + case ARGB: + pixel = is.read() | + (is.read() << 8) | (is.read() << 16) | (is.read() << 24); + break; + } + for (int i = 0; i < num; i++) { + px[index++] = pixel; + if (index == px.length) break; + } + } else { // write up to 127 bytes as uncompressed + num += 1; + switch (format) { + case ALPHA: + for (int i = 0; i < num; i++) { + px[index++] = is.read(); + } + break; + case RGB: + for (int i = 0; i < num; i++) { + px[index++] = 0xFF000000 | + is.read() | (is.read() << 8) | (is.read() << 16); + //(is.read() << 16) | (is.read() << 8) | is.read(); + } + break; + case ARGB: + for (int i = 0; i < num; i++) { + px[index++] = is.read() | //(is.read() << 24) | + (is.read() << 8) | (is.read() << 16) | (is.read() << 24); + //(is.read() << 16) | (is.read() << 8) | is.read(); + } + break; + } + } + } + + if (reversed) { + int[] temp = new int[w]; + for (int y = 0; y < h/2; y++) { + int z = (h-1) - y; + System.arraycopy(px, y*w, temp, 0, w); + System.arraycopy(px, z*w, px, y*w, w); + System.arraycopy(temp, 0, px, z*w, w); + } + } + } + + return outgoing; + } + + + + ////////////////////////////////////////////////////////////// + + // FONT I/O + + + /* + Hashtable fontTable; + */ + + /** + * Set the font based on its filename. This is less than efficient + * than using loadFont because there's no way to unload it from memory, + * but it's useful for beginners. + */ + /* + public void textFont(String filename) { + if (filename.toLowerCase().indexOf(".vlw") == -1) { + System.err.println("textFont() needs the filename of a .vlw font"); + } else { + textFont(tableFont(filename)); + } + } + */ + + + /** + * Set the font based on its filename. This is less than efficient + * than using loadFont because there's no way to unload it from memory, + * but it's useful for beginners. + */ + /* + public void textFont(String filename, float size) { + if (filename.toLowerCase().indexOf(".vlw") == -1) { + System.err.println("textFont() needs the filename of a .vlw font"); + } else { + textFont(tableFont(filename), size); + } + } + */ + + + /* + protected PFont tableFont(String filename) { + if (fontTable == null) fontTable = new Hashtable(); + + PFont font = (PFont) fontTable.get(filename); + if (font != null) return font; + + font = loadFont(filename); + return font; + } + */ + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public PFont loadFont(String filename) { + //if (g == null) { // just for good measure + //die("loadFont() only be used inside setup() or draw()"); + //} + + try { + String lower = filename.toLowerCase(); + InputStream input = openStream(filename); + + // For compatability with earlier releases of Processing + if (lower.endsWith(".vlw.gz")) { + input = new GZIPInputStream(input); + + } else if (!lower.endsWith(".vlw")) { + // this gets thrown down below + throw new IOException("I don't know how to load a font named " + + filename); + } + return new PFont(input); + + } catch (Exception e) { + die("Could not load font " + filename + ". " + + "Make sure that the font has been copied " + + "to the data folder of your sketch.", e); + } + return null; + } + + + public PFont createFont(String name, float size) { + return createFont(name, size, true, PFont.DEFAULT_CHARSET); + } + + + public PFont createFont(String name, float size, boolean smooth) { + return createFont(name, size, smooth, PFont.DEFAULT_CHARSET); + } + + + /** + * Create a .vlw font on the fly from either a font name that's + * installed on the system, or from a .ttf or .otf that's inside + * the data folder of this sketch. + *

+ * Only works with Java 1.3 or later. Many .otf fonts don't seem + * to be supported by Java, perhaps because they're CFF based? + *

+ * Font names are inconsistent across platforms and Java versions. + * On Mac OS X, Java 1.3 uses the font menu name of the font, + * whereas Java 1.4 uses the PostScript name of the font. Java 1.4 + * on OS X will also accept the font menu name as well. On Windows, + * it appears that only the menu names are used, no matter what + * Java version is in use. Naming system unknown/untested for 1.5. + *

+ * Use 'null' for the charset if you want to use any of the 65,536 + * unicode characters that exist in the font. Note that this can + * produce an enormous file or may cause an OutOfMemoryError. + */ + public PFont createFont(String name, float size, + boolean smooth, char charset[]) { + if (PApplet.javaVersion < 1.3f) { + throw new RuntimeException("Can only create fonts with " + + "Java 1.3 or higher"); + } + + String lowerName = name.toLowerCase(); + Font font = null; + + try { + Method deriveFontMethod = + Font.class.getMethod("deriveFont", + new Class[] { Float.TYPE }); + Float floatSize = new Float(size); + + if (lowerName.endsWith(".otf") || lowerName.endsWith(".ttf")) { + InputStream stream = openStream(name); + if (stream == null) { + System.err.println("The font \"" + name + "\" " + + "is missing or inaccessible, make sure " + + "the URL is valid or that the file has been " + + "added to your sketch and is readable."); + return null; + } + + //font = Font.createFont(Font.TRUETYPE_FONT, openStream(name)); + Method createFontMethod = + Font.class.getMethod("createFont", + new Class[] { Integer.TYPE, + InputStream.class }); + Field ttf = Font.class.getField("TRUETYPE_FONT"); + Integer ttfInteger = new Integer(ttf.getInt(ttf)); + Font baseFont = (Font) + createFontMethod.invoke(name, + new Object[] { ttfInteger, + openStream(name) }); + font = (Font) deriveFontMethod.invoke(baseFont, + new Object[] { floatSize }); + } else { + Font baseFont = new Font(name, Font.PLAIN, 1); + font = (Font) + deriveFontMethod.invoke(baseFont, new Object[] { floatSize }); + } + + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Problem using createFont() " + + "with the file " + name); + } + return new PFont(font, smooth, charset); + } + + + + ////////////////////////////////////////////////////////////// + + // FILE INPUT + + + public File inputFile() { + return inputFile("Select a file..."); + } + + + public File inputFile(String prompt) { + Frame parentFrame = null; + Component comp = getParent(); + while (comp != null) { + if (comp instanceof Frame) { + parentFrame = (Frame) comp; + break; + } + comp = comp.getParent(); + } + return inputFile(prompt, parentFrame); + } + + + static public File inputFile(Frame parent) { + return inputFile("Select a file...", parent); + } + + + /** + * static version of inputFile usable by external classes. + *

+ * The parentFrame is the Frame that will guide the placement of + * the prompt window. If no Frame is available, just pass in null. + */ + // can't be static because it wants a host component + static public File inputFile(String prompt, Frame parentFrame) { + if (parentFrame == null) parentFrame = new Frame(); + FileDialog fd = new FileDialog(parentFrame, prompt, FileDialog.LOAD); + fd.setVisible(true); + + String directory = fd.getDirectory(); + String filename = fd.getFile(); + if (filename == null) return null; + return new File(directory, filename); + } + + + public File outputFile() { + return outputFile("Save as..."); + } + + + public File outputFile(String prompt) { + Frame parentFrame = null; + Component comp = getParent(); + while (comp != null) { + //System.out.println(comp + " " + comp.getClass()); + if (comp instanceof Frame) { + parentFrame = (Frame) comp; + break; + } + comp = comp.getParent(); + } + return outputFile(prompt, parentFrame); + } + + + + static public File outputFile(Frame parentFrame) { + return outputFile("Save as...", parentFrame); + } + + + /** + * static version of outputFile usable by external classes. + *

+ * The parentFrame is the Frame that will guide the placement of + * the prompt window. If no Frame is available, just pass in null. + */ + static public File outputFile(String prompt, Frame parentFrame) { + if (parentFrame == null) parentFrame = new Frame(); + FileDialog fd = new FileDialog(parentFrame, prompt, FileDialog.SAVE); + fd.setVisible(true); + + String directory = fd.getDirectory(); + String filename = fd.getFile(); + if (filename == null) return null; + return new File(directory, filename); + } + + + /** + * I want to read lines from a file. I have RSI from typing these + * eight lines of code so many times. + */ + public BufferedReader createReader(String filename) { + try { + InputStream is = openStream(filename); + if (is == null) { + System.err.println(filename + " does not exist or could not be read"); + return null; + } + return createReader(is); + + } catch (Exception e) { + if (filename == null) { + System.err.println("Filename passed to reader() was null"); + } else { + System.err.println("Couldn't create a reader for " + filename); + } + } + return null; + } + + + /** + * I want to read lines from a file. And I'm still annoyed. + */ + static public BufferedReader createReader(File file) { + try { + return createReader(new FileInputStream(file)); + + } catch (Exception e) { + if (file == null) { + throw new RuntimeException("File passed to reader() was null"); + } else { + e.printStackTrace(); + throw new RuntimeException("Couldn't create a reader for " + + file.getAbsolutePath()); + } + } + //return null; + } + + + /** + * I want to read lines from a stream. If I have to type the + * following lines any more I'm gonna send Sun my medical bills. + */ + static public BufferedReader createReader(InputStream input) { + InputStreamReader isr = new InputStreamReader(input); + return new BufferedReader(isr); + } + + + /** + * decode a gzip input stream + */ + static public InputStream gzipInput(InputStream input) { + try { + return new GZIPInputStream(input); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Problem with gzip input"); + } + //return null; + } + + + /** + * decode a gzip output stream + */ + static public OutputStream gzipOutput(OutputStream output) { + try { + return new GZIPOutputStream(output); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Problem with gzip output"); + } + //return null; + } + + + /** + * I want to print lines to a file. Why can't I? + */ + public PrintWriter createWriter(String filename) { + try { + return createWriter(new FileOutputStream(savePath(filename))); + + } catch (Exception e) { + if (filename == null) { + die("Filename passed to writer() was null", e); + } else { + die("Couldn't create a writer for " + filename, e); + } + } + return null; + } + + + /** + * I want to print lines to a file. I have RSI from typing these + * eight lines of code so many times. + */ + static public PrintWriter createWriter(File file) { + try { + return createWriter(new FileOutputStream(file)); + + } catch (Exception e) { + if (file == null) { + throw new RuntimeException("File passed to writer() was null"); + } else { + e.printStackTrace(); + throw new RuntimeException("Couldn't create a writer for " + + file.getAbsolutePath()); + } + } + //return null; + } + + + /** + * I want to print lines to a file. Why am I always explaining myself? + * It's the JavaSoft API engineers who need to explain themselves. + */ + static public PrintWriter createWriter(OutputStream output) { + OutputStreamWriter osw = new OutputStreamWriter(output); + return new PrintWriter(osw); + } + + + /** + * Simplified method to open a Java InputStream. + *

+ * This method is useful if you want to use the facilities provided + * by PApplet to easily open things from the data folder or from a URL, + * but want an InputStream object so that you can use other Java + * methods to take more control of how the stream is read. + *

+ * If the requested item doesn't exist, null is returned. + * (Prior to 0096, die() would be called, killing the applet) + *

+ * For 0096+, the "data" folder is exported intact with subfolders, + * and openStream() properly handles subdirectories from the data folder + *

+ * If not online, this will also check to see if the user is asking + * for a file whose name isn't properly capitalized. This helps prevent + * issues when a sketch is exported to the web, where case sensitivity + * matters, as opposed to Windows and the Mac OS default where + * case sensitivity is preserved but ignored. + *

+ * It is strongly recommended that libraries use this method to open + * data files, so that the loading sequence is handled in the same way + * as functions like loadBytes(), loadImage(), etc. + *

+ * The filename passed in can be: + *

+ */ + public InputStream openStream(String filename) { + InputStream stream = null; + + if (filename == null) return null; + + if (filename.length() == 0) { + // an error will be called by the parent function + //System.err.println("The filename passed to openStream() was empty."); + return null; + } + + // safe to check for this as a url first. this will prevent online + // access logs from being spammed with GET /sketchfolder/http://blahblah + try { + URL url = new URL(filename); + stream = url.openStream(); + return stream; + + } catch (MalformedURLException mfue) { + // not a url, that's fine + + } catch (FileNotFoundException fnfe) { + // Java 1.5 likes to throw this when URL not available. (fix for 0119) + // http://dev.processing.org/bugs/show_bug.cgi?id=403 + + } catch (IOException e) { + // changed for 0117, shouldn't be throwing exception + e.printStackTrace(); + //System.err.println("Error downloading from URL " + filename); + return null; + //throw new RuntimeException("Error downloading from URL " + filename); + } + + // using getClassLoader() prevents java from converting dots + // to slashes or requiring a slash at the beginning. + // (a slash as a prefix means that it'll load from the root of + // the jar, rather than trying to dig into the package location) + ClassLoader cl = getClass().getClassLoader(); + + // by default, data files are exported to the root path of the jar. + // (not the data folder) so check there first. + stream = cl.getResourceAsStream("data/" + filename); + if (stream != null) { + String cn = stream.getClass().getName(); + // this is an irritation of sun's java plug-in, which will return + // a non-null stream for an object that doesn't exist. like all good + // things, this is probably introduced in java 1.5. awesome! + // http://dev.processing.org/bugs/show_bug.cgi?id=359 + if (!cn.equals("sun.plugin.cache.EmptyInputStream")) { + return stream; + } + } + + // when used with an online script, also need to check without the + // data folder, in case it's not in a subfolder called 'data' + // http://dev.processing.org/bugs/show_bug.cgi?id=389 + stream = cl.getResourceAsStream(filename); + if (stream != null) { + String cn = stream.getClass().getName(); + if (!cn.equals("sun.plugin.cache.EmptyInputStream")) { + return stream; + } + } + + // handle case sensitivity check + if (!online) { + try { + // first see if it's in a data folder + File file = new File(dataPath(filename)); + if (!file.exists()) { + // next see if it's just in this folder + file = new File(sketchPath, filename); + } + if (file.exists()) { + try { + String filePath = file.getCanonicalPath(); + String filenameActual = new File(filePath).getName(); + // make sure there isn't a subfolder prepended to the name + String filenameShort = new File(filename).getName(); + // if the actual filename is the same, but capitalized + // differently, warn the user. + //if (filenameActual.equalsIgnoreCase(filenameShort) && + //!filenameActual.equals(filenameShort)) { + if (!filenameActual.equals(filenameShort)) { + throw new RuntimeException("This file is named " + + filenameActual + " not " + + filename + ". Re-name it " + + "or change your code."); + } + } catch (IOException e) { } + } + + // if this file is ok, may as well just load it + stream = new FileInputStream(file); + if (stream != null) return stream; + + // have to break these out because a general Exception might + // catch the RuntimeException being thrown above + } catch (IOException ioe) { + } catch (SecurityException se) { } + } + + try { + // attempt to load from a local file, used when running as + // an application, or as a signed applet + try { // first try to catch any security exceptions + try { + stream = new FileInputStream(dataPath(filename)); + if (stream != null) return stream; + } catch (IOException e2) { } + + try { + stream = new FileInputStream(sketchPath(filename)); + if (stream != null) return stream; + } catch (Exception e) { } // ignored + + try { + stream = new FileInputStream(filename); + if (stream != null) return stream; + } catch (IOException e1) { } + + } catch (SecurityException se) { } // online, whups + + } catch (Exception e) { + //die(e.getMessage(), e); + e.printStackTrace(); + } + return null; + } + + + static public InputStream openStream(File file) { + try { + return new FileInputStream(file); + + } catch (IOException e) { + if (file == null) { + throw new RuntimeException("File passed to openStream() was null"); + + } else { + e.printStackTrace(); + throw new RuntimeException("Couldn't openStream() for " + + file.getAbsolutePath()); + } + } + } + + + public InputStream openStreamGZ(String filename) { + try { + return new GZIPInputStream(openStream(filename)); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Couldn't openStreamGZ() for " + + filename); + } + } + + + static public InputStream openStreamGZ(File file) { + try { + return new GZIPInputStream(openStream(file)); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Couldn't openStreamGZ() for " + + file.getAbsolutePath()); + } + } + + + public byte[] loadBytes(String filename) { + InputStream is = openStream(filename); + if (is != null) return loadBytes(is); + + System.err.println("The file \"" + filename + "\" " + + "is missing or inaccessible, make sure " + + "the URL is valid or that the file has been " + + "added to your sketch and is readable."); + return null; + } + + + static public byte[] loadBytes(InputStream input) { + try { + BufferedInputStream bis = new BufferedInputStream(input); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + int c = bis.read(); + while (c != -1) { + out.write(c); + c = bis.read(); + } + return out.toByteArray(); + + } catch (IOException e) { + e.printStackTrace(); + //throw new RuntimeException("Couldn't load bytes from stream"); + } + return null; + } + + + static public String[] loadStrings(File file) { + InputStream is = openStream(file); + if (is != null) return loadStrings(is); + return null; + } + + + /** + * Load data from a file and shove it into a String array. + *

+ * Exceptions are handled internally, when an error, occurs, an + * exception is printed to the console and 'null' is returned, + * but the program continues running. This is a tradeoff between + * 1) showing the user that there was a problem but 2) not requiring + * that all i/o code is contained in try/catch blocks, for the sake + * of new users (or people who are just trying to get things done + * in a "scripting" fashion. If you want to handle exceptions, + * use Java methods for I/O. + */ + public String[] loadStrings(String filename) { + InputStream is = openStream(filename); + if (is != null) return loadStrings(is); + + System.err.println("The file \"" + filename + "\" " + + "is missing or inaccessible, make sure " + + "the URL is valid or that the file has been " + + "added to your sketch and is readable."); + return null; + } + + + static public String[] loadStrings(InputStream input) { + try { + BufferedReader reader = + new BufferedReader(new InputStreamReader(input)); + + String lines[] = new String[100]; + int lineCount = 0; + String line = null; + while ((line = reader.readLine()) != null) { + if (lineCount == lines.length) { + String temp[] = new String[lineCount << 1]; + System.arraycopy(lines, 0, temp, 0, lineCount); + lines = temp; + } + lines[lineCount++] = line; + } + reader.close(); + + if (lineCount == lines.length) { + return lines; + } + + // resize array to appropriate amount for these lines + String output[] = new String[lineCount]; + System.arraycopy(lines, 0, output, 0, lineCount); + return output; + + } catch (IOException e) { + e.printStackTrace(); + //throw new RuntimeException("Error inside loadStrings()"); + } + return null; + } + + + + ////////////////////////////////////////////////////////////// + + // FILE OUTPUT + + + /** + * Save the contents of a stream to a file in the sketch folder. + * This is basically saveBytes(loadBytes(), blah), but done + * in a less confusing manner. + */ + public void saveStream(String filename, String stream) { + saveBytes(filename, loadBytes(stream)); + } + + + /** + * Identical to the other saveStream(), but writes to a File + * object, for greater control over the file location. + */ + public void saveStream(File file, String stream) { + saveBytes(file, loadBytes(stream)); + } + + + /** + * Saves bytes to a file to inside the sketch folder. + * The filename can be a relative path, i.e. "poo/bytefun.txt" + * would save to a file named "bytefun.txt" to a subfolder + * called 'poo' inside the sketch folder. If the in-between + * subfolders don't exist, they'll be created. + */ + public void saveBytes(String filename, byte buffer[]) { + try { + String location = savePath(filename); + FileOutputStream fos = new FileOutputStream(location); + saveBytes(fos, buffer); + fos.close(); + + } catch (IOException e) { + System.err.println("error saving bytes to " + filename); + e.printStackTrace(); + } + } + + /** + * Saves bytes to a specific File location specified by the user. + */ + static public void saveBytes(File file, byte buffer[]) { + try { + String filename = file.getAbsolutePath(); + createPath(filename); + FileOutputStream fos = new FileOutputStream(file); + saveBytes(fos, buffer); + fos.close(); + + } catch (IOException e) { + System.err.println("error saving bytes to " + file); + e.printStackTrace(); + } + } + + + /** + * Spews a buffer of bytes to an OutputStream. + */ + static public void saveBytes(OutputStream output, byte buffer[]) { + try { + //BufferedOutputStream bos = new BufferedOutputStream(output); + output.write(buffer); + output.flush(); + + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Couldn't save bytes"); + } + } + + // + + public void saveStrings(String filename, String strings[]) { + try { + String location = savePath(filename); + FileOutputStream fos = new FileOutputStream(location); + saveStrings(fos, strings); + fos.close(); + + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("saveStrings() failed: " + e.getMessage()); + } + } + + + static public void saveStrings(File file, String strings[]) { + try { + String location = file.getAbsolutePath(); + createPath(location); + FileOutputStream fos = new FileOutputStream(location); + saveStrings(fos, strings); + fos.close(); + + } catch (IOException e) { + System.err.println("error while saving strings"); + e.printStackTrace(); + } + } + + + static public void saveStrings(OutputStream output, String strings[]) { + PrintWriter writer = + new PrintWriter(new OutputStreamWriter(output)); + for (int i = 0; i < strings.length; i++) { + writer.println(strings[i]); + } + writer.flush(); + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Prepend the sketch folder path to the filename (or path) that is + * passed in. External libraries should use this function to save to + * the sketch folder. + *

+ * Note that when running as an applet inside a web browser, + * the sketchPath will be set to null, because security restrictions + * prevent applets from accessing that information. + *

+ * This will also cause an error if the sketch is not inited properly, + * meaning that init() was never called on the PApplet when hosted + * my some other main() or by other code. For proper use of init(), + * see the examples in the main description text for PApplet. + */ + public String sketchPath(String where) { + if (sketchPath == null) { + throw new RuntimeException("The applet was not inited properly, " + + "or security restrictions prevented " + + "it from determining its path."); + } + // isAbsolute() could throw an access exception, but so will writing + // to the local disk using the sketch path, so this is safe here. + // for 0120, added a try/catch anyways. + try { + if (new File(where).isAbsolute()) return where; + } catch (Exception e) { } + + return sketchPath + File.separator + where; + } + + + /** + * Returns a path inside the applet folder to save to, + * just like sketchPath(), but also creates any in-between + * folders so that things save properly. + *

+ * All saveXxxx() functions use the path to the sketch folder, rather than + * its data folder. Once exported, the data folder will be found inside the + * jar file of the exported application or applet. In this case, it's not + * possible to save data into the jar file, because it will often be running + * from a server, or marked in-use if running from a local file system. + * With this in mind, saving to the data path doesn't make sense anyway. + * If you know you're running locally, and want to save to the data folder, + * use saveXxxx("data/blah.dat"). + */ + public String savePath(String where) { + String filename = sketchPath(where); + createPath(filename); + return filename; + } + + + /** + * Return a full path to an item in the data folder. + *

+ * In this method, the data path is defined not as the applet's actual + * data path, but a folder titled "data" in the sketch's working + * directory. This is because in an application, the "data" folder is + * exported as part of the jar file, and it's not as though you're gonna + * write into the jar file itself. If you need to get things out of + * the jar file, you should use openStream(). + */ + public String dataPath(String where) { + // isAbsolute() could throw an access exception, but so will writing + // to the local disk using the sketch path, so this is safe here. + if (new File(where).isAbsolute()) return where; + + return sketchPath + File.separator + "data" + File.separator + where; + } + + + /** + * Takes a path and creates any in-between folders if they don't + * already exist. Useful when trying to save to a subfolder that + * may not actually exist. + */ + static public void createPath(String filename) { + File file = new File(filename); + String parent = file.getParent(); + if (parent != null) { + File unit = new File(parent); + if (!unit.exists()) unit.mkdirs(); + } + } + + + ////////////////////////////////////////////////////////////// + + // SORT + + int sort_mode; + + static final int BYTES = 1; + static final int CHARS = 2; + static final int INTS = 3; + static final int FLOATS = 4; + static final int STRINGS = 5; + static final int OBJECTS = 6; + + byte sort_bytes[]; + char sort_chars[]; + int sort_ints[]; + float sort_floats[]; + String sort_strings[]; + + Object sortObject; + Method swapMethod; + Method compareMethod; + + public byte[] sort(byte what[]) { + return sort(what, what.length); + } + + public char[] sort(char what[]) { + return sort(what, what.length); + } + + public int[] sort(int what[]) { + return sort(what, what.length); + } + + public float[] sort(float what[]) { + return sort(what, what.length); + } + + public String[] sort(String what[]) { + return sort(what, what.length); + } + + // + + public byte[] sort(byte what[], int count) { + if (count == 0) return null; + sort_mode = BYTES; + sort_bytes = new byte[count]; + System.arraycopy(what, 0, sort_bytes, 0, count); + sort_internal(0, count-1); + return sort_bytes; + } + + public char[] sort(char what[], int count) { + if (count == 0) return null; + sort_mode = CHARS; + sort_chars = new char[count]; + System.arraycopy(what, 0, sort_chars, 0, count); + sort_internal(0, count-1); + return sort_chars; + } + + public int[] sort(int what[], int count) { + if (count == 0) return null; + sort_mode = INTS; + sort_ints = new int[count]; + System.arraycopy(what, 0, sort_ints, 0, count); + sort_internal(0, count-1); + return sort_ints; + } + + public float[] sort(float what[], int count) { + if (count == 0) return null; + sort_mode = FLOATS; + sort_floats = new float[count]; + System.arraycopy(what, 0, sort_floats, 0, count); + sort_internal(0, count-1); + return sort_floats; + } + + public String[] sort(String what[], int count) { + if (count == 0) return null; + sort_mode = STRINGS; + sort_strings = new String[count]; + System.arraycopy(what, 0, sort_strings, 0, count); + sort_internal(0, count-1); + return sort_strings; + } + + /* + public void sort(Object what, int count) { + if (count == 0) return null; + sort_mode = OBJECTS; + sort_strings = new String[count]; + System.arraycopy(what, 0, sort_strings, 0, count); + sort_internal(0, count-1); + return sort_strings; + } + */ + + // + + protected void sort_internal(int i, int j) { + int pivotIndex = (i+j)/2; + sort_swap(pivotIndex, j); + int k = sort_partition(i-1, j); + sort_swap(k, j); + if ((k-i) > 1) sort_internal(i, k-1); + if ((j-k) > 1) sort_internal(k+1, j); + } + + + protected int sort_partition(int left, int right) { + int pivot = right; + do { + while (sort_compare(++left, pivot) < 0) { } + while ((right != 0) && (sort_compare(--right, pivot) > 0)) { } + sort_swap(left, right); + } while (left < right); + sort_swap(left, right); + return left; + } + + + protected void sort_swap(int a, int b) { + switch (sort_mode) { + case BYTES: + byte btemp = sort_bytes[a]; + sort_bytes[a] = sort_bytes[b]; + sort_bytes[b] = btemp; + break; + case CHARS: + char ctemp = sort_chars[a]; + sort_chars[a] = sort_chars[b]; + sort_chars[b] = ctemp; + break; + case INTS: + int itemp = sort_ints[a]; + sort_ints[a] = sort_ints[b]; + sort_ints[b] = itemp; + break; + case FLOATS: + float ftemp = sort_floats[a]; + sort_floats[a] = sort_floats[b]; + sort_floats[b] = ftemp; + break; + case STRINGS: + String stemp = sort_strings[a]; + sort_strings[a] = sort_strings[b]; + sort_strings[b] = stemp; + break; + case OBJECTS: + try { + Object[] params = new Object[] { new Integer(a), new Integer(b) }; + swapMethod.invoke(sortObject, params); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + + protected float sort_compare(int a, int b) { + switch (sort_mode) { + case BYTES: + return sort_bytes[a] - sort_bytes[b]; + case CHARS: + return sort_chars[a] - sort_chars[b]; + case INTS: + return sort_ints[a] - sort_ints[b]; + case FLOATS: + // can't just cast to an int because 0.2 and 0.4 would + // just appear to be the same thing. no good. + //if (sort_floats[a] < sort_floats[b]) return -1; + //return (sort_floats[a] == sort_floats[b]) ? 0 : 1; + return sort_floats[a] - sort_floats[b]; + case STRINGS: + return sort_strings[a].compareTo(sort_strings[b]); + case OBJECTS: + try { + Object[] params = new Object[] { new Integer(a), new Integer(b) }; + Float output = (Float) compareMethod.invoke(sortObject, params); + return output.floatValue(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return 0; + } + + + ////////////////////////////////////////////////////////////// + + + public void sort(Object o, int count) { + Class c = o.getClass(); + try { + Class[] params = new Class[] { Integer.TYPE, Integer.TYPE }; + // takes two ints, returns a float + compareMethod = c.getMethod("sortCompare", params); + // takes two ints, returns void + swapMethod = c.getMethod("sortSwap", params); + // start the sort + sortObject = o; + sort_mode = OBJECTS; + sort_internal(0, count-1); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + + ////////////////////////////////////////////////////////////// + + // ARRAY UTILITIES + + + /** + * Calls System.arraycopy(), included here so that we can + * avoid people needing to learn about the System object + * before they can just copy an array. + */ + static public void arraycopy(Object src, int srcPosition, + Object dst, int dstPosition, + int length) { + System.arraycopy(src, srcPosition, dst, dstPosition, length); + } + + + /** + * Convenience method for arraycopy(). + * Identical to arraycopy(src, 0, dst, 0, length); + */ + static public void arraycopy(Object src, Object dst, int length) { + System.arraycopy(src, 0, dst, 0, length); + } + + + /** + * Shortcut to copy the entire contents of + * the source into the destination array. + * Identical to arraycopy(src, 0, dst, 0, src.length); + */ + static public void arraycopy(Object src, Object dst) { + System.arraycopy(src, 0, dst, 0, Array.getLength(src)); + } + + // + + static public boolean[] expand(boolean list[]) { + return expand(list, list.length << 1); + } + + static public boolean[] expand(boolean list[], int newSize) { + boolean temp[] = new boolean[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public byte[] expand(byte list[]) { + return expand(list, list.length << 1); + } + + static public byte[] expand(byte list[], int newSize) { + byte temp[] = new byte[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public char[] expand(char list[]) { + return expand(list, list.length << 1); + } + + static public char[] expand(char list[], int newSize) { + char temp[] = new char[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public int[] expand(int list[]) { + return expand(list, list.length << 1); + } + + static public int[] expand(int list[], int newSize) { + int temp[] = new int[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public float[] expand(float list[]) { + return expand(list, list.length << 1); + } + + static public float[] expand(float list[], int newSize) { + float temp[] = new float[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public String[] expand(String list[]) { + return expand(list, list.length << 1); + } + + static public String[] expand(String list[], int newSize) { + String temp[] = new String[newSize]; + // in case the new size is smaller than list.length + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public Object expand(Object array) { + return expand(array, Array.getLength(array) << 1); + } + + static public Object expand(Object list, int newSize) { + Class type = list.getClass().getComponentType(); + Object temp = Array.newInstance(type, newSize); + System.arraycopy(list, 0, temp, 0, + Math.min(Array.getLength(list), newSize)); + return temp; + } + + // + + // contract() has been removed in revision 0124, use subset() instead. + // (expand() is also functionally equivalent) + + // + + static public byte[] append(byte b[], byte value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public char[] append(char b[], char value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public int[] append(int b[], int value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public float[] append(float b[], float value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public String[] append(String b[], String value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public Object append(Object b, Object value) { + int length = Array.getLength(b); + b = expand(b, length + 1); + Array.set(b, length, value); + return b; + } + + // + + static public boolean[] shorten(boolean list[]) { + return subset(list, 0, list.length-1); + } + + static public byte[] shorten(byte list[]) { + return subset(list, 0, list.length-1); + } + + static public char[] shorten(char list[]) { + return subset(list, 0, list.length-1); + } + + static public int[] shorten(int list[]) { + return subset(list, 0, list.length-1); + } + + static public float[] shorten(float list[]) { + return subset(list, 0, list.length-1); + } + + static public String[] shorten(String list[]) { + return subset(list, 0, list.length-1); + } + + static public Object shorten(Object list) { + int length = Array.getLength(list); + return subset(list, 0, length - 1); + } + + // + + static final public boolean[] splice(boolean list[], + boolean v, int index) { + boolean outgoing[] = new boolean[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public boolean[] splice(boolean list[], + boolean v[], int index) { + boolean outgoing[] = new boolean[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public byte[] splice(byte list[], + byte v, int index) { + byte outgoing[] = new byte[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public byte[] splice(byte list[], + byte v[], int index) { + byte outgoing[] = new byte[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public char[] splice(char list[], + char v, int index) { + char outgoing[] = new char[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public char[] splice(char list[], + char v[], int index) { + char outgoing[] = new char[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public int[] splice(int list[], + int v, int index) { + int outgoing[] = new int[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public int[] splice(int list[], + int v[], int index) { + int outgoing[] = new int[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public float[] splice(float list[], + float v, int index) { + float outgoing[] = new float[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public float[] splice(float list[], + float v[], int index) { + float outgoing[] = new float[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public String[] splice(String list[], + String v, int index) { + String outgoing[] = new String[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public String[] splice(String list[], + String v[], int index) { + String outgoing[] = new String[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public Object splice(Object list, Object v, int index) { + Object[] outgoing = null; + int length = Array.getLength(list); + + // check whether is an array or not, and if so, treat as such + if (list.getClass().getName().charAt(0) == '[') { + int vlength = Array.getLength(v); + outgoing = new Object[length + vlength]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, vlength); + System.arraycopy(list, index, outgoing, index + vlength, length - index); + + } else { + outgoing = new Object[length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + Array.set(outgoing, index, v); + System.arraycopy(list, index, outgoing, index + 1, length - index); + } + return outgoing; + } + + // + + static public boolean[] subset(boolean list[], int start) { + return subset(list, start, list.length - start); + } + + static public boolean[] subset(boolean list[], int start, int count) { + boolean output[] = new boolean[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public byte[] subset(byte list[], int start) { + return subset(list, start, list.length - start); + } + + static public byte[] subset(byte list[], int start, int count) { + byte output[] = new byte[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public char[] subset(char list[], int start) { + return subset(list, start, list.length - start); + } + + static public char[] subset(char list[], int start, int count) { + char output[] = new char[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public int[] subset(int list[], int start) { + return subset(list, start, list.length - start); + } + + static public int[] subset(int list[], int start, int count) { + int output[] = new int[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public float[] subset(float list[], int start) { + return subset(list, start, list.length - start); + } + + static public float[] subset(float list[], int start, int count) { + float output[] = new float[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public String[] subset(String list[], int start) { + return subset(list, start, list.length - start); + } + + static public String[] subset(String list[], int start, int count) { + String output[] = new String[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public Object subset(Object list, int start) { + int length = Array.getLength(list); + int count = length - start; + Class type = list.getClass().getComponentType(); + Object outgoing = Array.newInstance(type, count); + System.arraycopy(list, 0, outgoing, 0, count); + return outgoing; + } + + static public Object subset(Object list, int start, int count) { + //int length = Array.getLength(list); + Class type = list.getClass().getComponentType(); + Object outgoing = Array.newInstance(type, count); + System.arraycopy(list, start, outgoing, 0, count); + return outgoing; + } + + // + + static public boolean[] concat(boolean a[], boolean b[]) { + boolean c[] = new boolean[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public byte[] concat(byte a[], byte b[]) { + byte c[] = new byte[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public char[] concat(char a[], char b[]) { + char c[] = new char[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public int[] concat(int a[], int b[]) { + int c[] = new int[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public float[] concat(float a[], float b[]) { + float c[] = new float[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public String[] concat(String a[], String b[]) { + String c[] = new String[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public Object concat(Object a, Object b) { + Class type = a.getClass().getComponentType(); + int alength = Array.getLength(a); + int blength = Array.getLength(b); + Object outgoing = Array.newInstance(type, alength + blength); + System.arraycopy(a, 0, outgoing, 0, alength); + System.arraycopy(b, 0, outgoing, alength, blength); + return outgoing; + } + + // + + static public boolean[] reverse(boolean list[]) { + boolean outgoing[] = new boolean[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public byte[] reverse(byte list[]) { + byte outgoing[] = new byte[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public char[] reverse(char list[]) { + char outgoing[] = new char[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public int[] reverse(int list[]) { + int outgoing[] = new int[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public float[] reverse(float list[]) { + float outgoing[] = new float[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public String[] reverse(String list[]) { + String outgoing[] = new String[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public Object reverse(Object list) { + Class type = list.getClass().getComponentType(); + int length = Array.getLength(list); + Object outgoing = Array.newInstance(type, length); + for (int i = 0; i < length; i++) { + Array.set(outgoing, i, Array.get(list, (length - 1) - i)); + } + return outgoing; + } + + + + ////////////////////////////////////////////////////////////// + + // STRINGS + + + /** + * Remove whitespace characters from the beginning and ending + * of a String. Works like String.trim() but includes the + * unicode nbsp character as well. + */ + static public String trim(String str) { + return str.replace('\u00A0', ' ').trim(); + + /* + int left = 0; + int right = str.length() - 1; + + while ((left <= right) && + (WHITESPACE.indexOf(str.charAt(left)) != -1)) left++; + if (left == right) return ""; + + while (WHITESPACE.indexOf(str.charAt(right)) != -1) --right; + + return str.substring(left, right-left+1); + */ + } + + + /** + * Trim the whitespace from a String array. This returns a new + * array and does not affect the passed-in array. + */ + static public String[] trim(String[] array) { + String[] outgoing = new String[array.length]; + for (int i = 0; i < array.length; i++) { + outgoing[i] = array[i].replace('\u00A0', ' ').trim(); + } + return outgoing; + } + + + /** + * Join an array of Strings together as a single String, + * separated by the whatever's passed in for the separator. + */ + static public String join(String str[], char separator) { + return join(str, String.valueOf(separator)); + } + + + /** + * Join an array of Strings together as a single String, + * separated by the whatever's passed in for the separator. + *

+ * To use this on numbers, first pass the array to nf() or nfs() + * to get a list of String objects, then use join on that. + *

+   * e.g. String stuff[] = { "apple", "bear", "cat" };
+   *      String list = join(stuff, ", ");
+   *      // list is now "apple, bear, cat"
+ */ + static public String join(String str[], String separator) { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < str.length; i++) { + if (i != 0) buffer.append(separator); + buffer.append(str[i]); + } + return buffer.toString(); + } + + + /** + * Split the provided String at wherever whitespace occurs. + * Multiple whitespace (extra spaces or tabs or whatever) + * between items will count as a single break. + *

+ * The whitespace characters are "\t\n\r\f", which are the defaults + * for java.util.StringTokenizer, plus the unicode non-breaking space + * character, which is found commonly on files created by or used + * in conjunction with Mac OS X (character 160, or 0x00A0 in hex). + *

+   * i.e. splitTokens("a b") -> { "a", "b" }
+   *      splitTokens("a    b") -> { "a", "b" }
+   *      splitTokens("a\tb") -> { "a", "b" }
+   *      splitTokens("a \t  b  ") -> { "a", "b" }
+ */ + static public String[] splitTokens(String what) { + return splitTokens(what, WHITESPACE); + } + + + /** + * Splits a string into pieces, using any of the chars in the + * String 'delim' as separator characters. For instance, + * in addition to white space, you might want to treat commas + * as a separator. The delimeter characters won't appear in + * the returned String array. + *
+   * i.e. splitTokens("a, b", " ,") -> { "a", "b" }
+   * 
+ * To include all the whitespace possibilities, use the variable + * WHITESPACE, found in PConstants: + *
+   * i.e. splitTokens("a   | b", WHITESPACE + "|");  ->  { "a", "b" }
+ */ + static public String[] splitTokens(String what, String delim) { + StringTokenizer toker = new StringTokenizer(what, delim); + String pieces[] = new String[toker.countTokens()]; + + int index = 0; + while (toker.hasMoreTokens()) { + pieces[index++] = toker.nextToken(); + } + return pieces; + } + + + /** + * Split a string into pieces along a specific character. + * Most commonly used to break up a String along tab characters. + *

+ * This operates differently than the others, where the + * single delimeter is the only breaking point, and consecutive + * delimeters will produce an empty string (""). This way, + * one can split on tab characters, but maintain the column + * alignments (of say an excel file) where there are empty columns. + */ + static public String[] split(String what, char delim) { + // do this so that the exception occurs inside the user's + // program, rather than appearing to be a bug inside split() + if (what == null) return null; + //return split(what, String.valueOf(delim)); // huh + + char chars[] = what.toCharArray(); + int splitCount = 0; //1; + for (int i = 0; i < chars.length; i++) { + if (chars[i] == delim) splitCount++; + } + // make sure that there is something in the input string + //if (chars.length > 0) { + // if the last char is a delimeter, get rid of it.. + //if (chars[chars.length-1] == delim) splitCount--; + // on second thought, i don't agree with this, will disable + //} + if (splitCount == 0) { + String splits[] = new String[1]; + splits[0] = new String(what); + return splits; + } + //int pieceCount = splitCount + 1; + String splits[] = new String[splitCount + 1]; + int splitIndex = 0; + int startIndex = 0; + for (int i = 0; i < chars.length; i++) { + if (chars[i] == delim) { + splits[splitIndex++] = + new String(chars, startIndex, i-startIndex); + startIndex = i + 1; + } + } + //if (startIndex != chars.length) { + splits[splitIndex] = + new String(chars, startIndex, chars.length-startIndex); + //} + return splits; + } + + + /** + * FIXME this is only temporary + */ + static public String[] split(String what, String delim) { + return what.split(delim); + } + + + + ////////////////////////////////////////////////////////////// + + // CASTING FUNCTIONS, INSERTED BY PREPROC + + + /** + * Convert a char to a boolean. 'T', 't', and '1' will become the + * boolean value true, while 'F', 'f', or '0' will become false. + */ + /* + static final public boolean parseBoolean(char what) { + return ((what == 't') || (what == 'T') || (what == '1')); + } + */ + + /** + *

Convert an integer to a boolean. Because of how Java handles upgrading + * numbers, this will also cover byte and char (as they will upgrade to + * an int without any sort of explicit cast).

+ *

The preprocessor will convert boolean(what) to parseBoolean(what).

+ * @return false if 0, true if any other number + */ + static final public boolean parseBoolean(int what) { + return (what != 0); + } + + /* + // removed because this makes no useful sense + static final public boolean parseBoolean(float what) { + return (what != 0); + } + */ + + /** + * Convert the string "true" or "false" to a boolean. + * @return true if 'what' is "true" or "TRUE", false otherwise + */ + static final public boolean parseBoolean(String what) { + return new Boolean(what).booleanValue(); + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + // removed, no need to introduce strange syntax from other languages + static final public boolean[] parseBoolean(char what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = + ((what[i] == 't') || (what[i] == 'T') || (what[i] == '1')); + } + return outgoing; + } + */ + + /** + * Convert a byte array to a boolean array. Each element will be + * evaluated identical to the integer case, where a byte equal + * to zero will return false, and any other value will return true. + * @return array of boolean elements + */ + static final public boolean[] parseBoolean(byte what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (what[i] != 0); + } + return outgoing; + } + + /** + * Convert an int array to a boolean array. An int equal + * to zero will return false, and any other value will return true. + * @return array of boolean elements + */ + static final public boolean[] parseBoolean(int what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (what[i] != 0); + } + return outgoing; + } + + /* + // removed, not necessary... if necessary, convert to int array first + static final public boolean[] parseBoolean(float what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (what[i] != 0); + } + return outgoing; + } + */ + + static final public boolean[] parseBoolean(String what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = new Boolean(what[i]).booleanValue(); + } + return outgoing; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public byte parseByte(boolean what) { + return what ? (byte)1 : 0; + } + + static final public byte parseByte(char what) { + return (byte) what; + } + + static final public byte parseByte(int what) { + return (byte) what; + } + + static final public byte parseByte(float what) { + return (byte) what; + } + + /* + // nixed, no precedent + static final public byte[] parseByte(String what) { // note: array[] + return what.getBytes(); + } + */ + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public byte[] parseByte(boolean what[]) { + byte outgoing[] = new byte[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = what[i] ? (byte)1 : 0; + } + return outgoing; + } + + static final public byte[] parseByte(char what[]) { + byte outgoing[] = new byte[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (byte) what[i]; + } + return outgoing; + } + + static final public byte[] parseByte(int what[]) { + byte outgoing[] = new byte[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (byte) what[i]; + } + return outgoing; + } + + static final public byte[] parseByte(float what[]) { + byte outgoing[] = new byte[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (byte) what[i]; + } + return outgoing; + } + + /* + static final public byte[][] parseByte(String what[]) { // note: array[][] + byte outgoing[][] = new byte[what.length][]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = what[i].getBytes(); + } + return outgoing; + } + */ + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + static final public char parseChar(boolean what) { // 0/1 or T/F ? + return what ? 't' : 'f'; + } + */ + + static final public char parseChar(byte what) { + return (char) (what & 0xff); + } + + static final public char parseChar(int what) { + return (char) what; + } + + /* + static final public char parseChar(float what) { // nonsensical + return (char) what; + } + + static final public char[] parseChar(String what) { // note: array[] + return what.toCharArray(); + } + */ + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + static final public char[] parseChar(boolean what[]) { // 0/1 or T/F ? + char outgoing[] = new char[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = what[i] ? 't' : 'f'; + } + return outgoing; + } + */ + + static final public char[] parseChar(byte what[]) { + char outgoing[] = new char[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (char) (what[i] & 0xff); + } + return outgoing; + } + + static final public char[] parseChar(int what[]) { + char outgoing[] = new char[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (char) what[i]; + } + return outgoing; + } + + /* + static final public char[] parseChar(float what[]) { // nonsensical + char outgoing[] = new char[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (char) what[i]; + } + return outgoing; + } + + static final public char[][] parseChar(String what[]) { // note: array[][] + char outgoing[][] = new char[what.length][]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = what[i].toCharArray(); + } + return outgoing; + } + */ + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public int parseInt(boolean what) { + return what ? 1 : 0; + } + + /** + * Note that parseInt() will un-sign a signed byte value. + */ + static final public int parseInt(byte what) { + return what & 0xff; + } + + /** + * Note that parseInt('5') is unlike String in the sense that it + * won't return 5, but the ascii value. This is because ((int) someChar) + * returns the ascii value, and parseInt() is just longhand for the cast. + */ + static final public int parseInt(char what) { + return what; + } + + /** + * Same as floor(), or an (int) cast. + */ + static final public int parseInt(float what) { + return (int) what; + } + + /** + * Parse a String into an int value. Returns 0 if the value is bad. + */ + static final public int parseInt(String what) { + return parseInt(what, 0); + } + + /** + * Parse a String to an int, and provide an alternate value that + * should be used when the number is invalid. + */ + static final public int parseInt(String what, int otherwise) { + try { + int offset = what.indexOf('.'); + if (offset == -1) { + return Integer.parseInt(what); + } else { + return Integer.parseInt(what.substring(0, offset)); + } + } catch (NumberFormatException e) { } + return otherwise; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public int[] parseInt(boolean what[]) { + int list[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + list[i] = what[i] ? 1 : 0; + } + return list; + } + + static final public int[] parseInt(byte what[]) { // note this unsigns + int list[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + list[i] = (what[i] & 0xff); + } + return list; + } + + static final public int[] parseInt(char what[]) { + int list[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + list[i] = what[i]; + } + return list; + } + + static public int[] parseInt(float what[]) { + int inties[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + inties[i] = (int)what[i]; + } + return inties; + } + + /** + * Make an array of int elements from an array of String objects. + * If the String can't be parsed as a number, it will be set to zero. + * + * String s[] = { "1", "300", "44" }; + * int numbers[] = parseInt(s); + * + * numbers will contain { 1, 300, 44 } + */ + static public int[] parseInt(String what[]) { + return parseInt(what, 0); + } + + /** + * Make an array of int elements from an array of String objects. + * If the String can't be parsed as a number, its entry in the + * array will be set to the value of the "missing" parameter. + * + * String s[] = { "1", "300", "apple", "44" }; + * int numbers[] = parseInt(s, 9999); + * + * numbers will contain { 1, 300, 9999, 44 } + */ + static public int[] parseInt(String what[], int missing) { + int output[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + try { + output[i] = Integer.parseInt(what[i]); + } catch (NumberFormatException e) { + output[i] = missing; + } + } + return output; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + static final public float parseFloat(boolean what) { + return what ? 1 : 0; + } + */ + + /** + * Convert an int to a float value. Also handles bytes because of + * Java's rules for upgrading values. + */ + static final public float parseFloat(int what) { // also handles byte + return (float)what; + } + + static final public float parseFloat(String what) { + return parseFloat(what, Float.NaN); + } + + static final public float parseFloat(String what, float otherwise) { + try { + return new Float(what).floatValue(); + } catch (NumberFormatException e) { } + + return otherwise; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + static final public float[] parseFloat(boolean what[]) { + float floaties[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + floaties[i] = what[i] ? 1 : 0; + } + return floaties; + } + + static final public float[] parseFloat(char what[]) { + float floaties[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + floaties[i] = (char) what[i]; + } + return floaties; + } + */ + + static final public float[] parseByte(byte what[]) { + float floaties[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + floaties[i] = what[i]; + } + return floaties; + } + + static final public float[] parseFloat(int what[]) { + float floaties[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + floaties[i] = what[i]; + } + return floaties; + } + + static final public float[] parseFloat(String what[]) { + return parseFloat(what, 0); + } + + static final public float[] parseFloat(String what[], float missing) { + float output[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + try { + output[i] = new Float(what[i]).floatValue(); + } catch (NumberFormatException e) { + output[i] = missing; + } + } + return output; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public String str(boolean x) { + return String.valueOf(x); + } + + static final public String str(byte x) { + return String.valueOf(x); + } + + static final public String str(char x) { + return String.valueOf(x); + } + + static final public String str(int x) { + return String.valueOf(x); + } + + static final public String str(float x) { + return String.valueOf(x); + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public String[] str(boolean x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x); + return s; + } + + static final public String[] str(byte x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x); + return s; + } + + static final public String[] str(char x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x); + return s; + } + + static final public String[] str(int x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x); + return s; + } + + static final public String[] str(float x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x); + return s; + } + + + ////////////////////////////////////////////////////////////// + + // INT NUMBER FORMATTING + + + /** + * Integer number formatter. + */ + static private NumberFormat int_nf; + static private int int_nf_digits; + static private boolean int_nf_commas; + + + static public String[] nf(int num[], int digits) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nf(num[i], digits); + } + return formatted; + } + + + static public String nf(int num, int digits) { + if ((int_nf != null) && + (int_nf_digits == digits) && + !int_nf_commas) { + return int_nf.format(num); + } + + int_nf = NumberFormat.getInstance(); + int_nf.setGroupingUsed(false); // no commas + int_nf_commas = false; + int_nf.setMinimumIntegerDigits(digits); + int_nf_digits = digits; + return int_nf.format(num); + } + + + static public String[] nfc(int num[]) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfc(num[i]); + } + return formatted; + } + + + static public String nfc(int num) { + if ((int_nf != null) && + (int_nf_digits == 0) && + int_nf_commas) { + return int_nf.format(num); + } + + int_nf = NumberFormat.getInstance(); + int_nf.setGroupingUsed(true); + int_nf_commas = true; + int_nf.setMinimumIntegerDigits(0); + int_nf_digits = 0; + return int_nf.format(num); + } + + + /** + * number format signed (or space) + * Formats a number but leaves a blank space in the front + * when it's positive so that it can be properly aligned with + * numbers that have a negative sign in front of them. + */ + static public String nfs(int num, int digits) { + return (num < 0) ? nf(num, digits) : (' ' + nf(num, digits)); + } + + static public String[] nfs(int num[], int digits) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfs(num[i], digits); + } + return formatted; + } + + // + + /** + * number format positive (or plus) + * Formats a number, always placing a - or + sign + * in the front when it's negative or positive. + */ + static public String nfp(int num, int digits) { + return (num < 0) ? nf(num, digits) : ('+' + nf(num, digits)); + } + + static public String[] nfp(int num[], int digits) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfp(num[i], digits); + } + return formatted; + } + + + + ////////////////////////////////////////////////////////////// + + // FLOAT NUMBER FORMATTING + + + static private NumberFormat float_nf; + static private int float_nf_left, float_nf_right; + static private boolean float_nf_commas; + + + static public String[] nf(float num[], int left, int right) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nf(num[i], left, right); + } + return formatted; + } + + + static public String nf(float num, int left, int right) { + if ((float_nf != null) && + (float_nf_left == left) && + (float_nf_right == right) && + !float_nf_commas) { + return float_nf.format(num); + } + + float_nf = NumberFormat.getInstance(); + float_nf.setGroupingUsed(false); + float_nf_commas = false; + + if (left != 0) float_nf.setMinimumIntegerDigits(left); + if (right != 0) { + float_nf.setMinimumFractionDigits(right); + float_nf.setMaximumFractionDigits(right); + } + float_nf_left = left; + float_nf_right = right; + return float_nf.format(num); + } + + + static public String[] nfc(float num[], int right) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfc(num[i], right); + } + return formatted; + } + + + static public String nfc(float num, int right) { + if ((float_nf != null) && + (float_nf_left == 0) && + (float_nf_right == right) && + float_nf_commas) { + return float_nf.format(num); + } + + float_nf = NumberFormat.getInstance(); + float_nf.setGroupingUsed(true); + float_nf_commas = true; + + if (right != 0) { + float_nf.setMinimumFractionDigits(right); + float_nf.setMaximumFractionDigits(right); + } + float_nf_left = 0; + float_nf_right = right; + return float_nf.format(num); + } + + + /** + * Number formatter that takes into account whether the number + * has a sign (positive, negative, etc) in front of it. + */ + static public String[] nfs(float num[], int left, int right) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfs(num[i], left, right); + } + return formatted; + } + + static public String nfs(float num, int left, int right) { + return (num < 0) ? nf(num, left, right) : (' ' + nf(num, left, right)); + } + + + static public String[] nfp(float num[], int left, int right) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfp(num[i], left, right); + } + return formatted; + } + + static public String nfp(float num, int left, int right) { + return (num < 0) ? nf(num, left, right) : ('+' + nf(num, left, right)); + } + + + + ////////////////////////////////////////////////////////////// + + // HEX/BINARY CONVERSION + + + static final public String hex(byte what) { + return hex(what, 2); + } + + static final public String hex(char what) { + return hex(what, 4); + } + + static final public String hex(int what) { + return hex(what, 8); + } + + static final public String hex(int what, int digits) { + String stuff = Integer.toHexString(what).toUpperCase(); + + int length = stuff.length(); + if (length > digits) { + return stuff.substring(length - digits); + + } else if (length < digits) { + return "00000000".substring(8 - (digits-length)) + stuff; + } + return stuff; + } + + static final public int unhex(String what) { + // has to parse as a Long so that it'll work for numbers bigger than 2^31 + return (int) (Long.parseLong(what, 16)); + } + + // + + /** + * Returns a String that contains the binary value of a byte. + * The returned value will always have 8 digits. + */ + static final public String binary(byte what) { + return binary(what, 8); + } + + /** + * Returns a String that contains the binary value of a char. + * The returned value will always have 16 digits because chars + * are two bytes long. + */ + static final public String binary(char what) { + return binary(what, 16); + } + + /** + * Returns a String that contains the binary value of an int. + * The length depends on the size of the number itself. + * An int can be up to 32 binary digits, but that seems like + * overkill for almost any situation, so this function just + * auto-size. If you want a specific number of digits (like all 32) + * use binary(int what, int digits) to specify how many digits. + */ + static final public String binary(int what) { + return Integer.toBinaryString(what); + //return binary(what, 32); + } + + /** + * Returns a String that contains the binary value of an int. + * The digits parameter determines how many digits will be used. + */ + static final public String binary(int what, int digits) { + String stuff = Integer.toBinaryString(what); + + int length = stuff.length(); + if (length > digits) { + return stuff.substring(length - digits); + + } else if (length < digits) { + int offset = 32 - (digits-length); + return "00000000000000000000000000000000".substring(offset) + stuff; + } + return stuff; + } + + + /** + * Unpack a binary String into an int. + * i.e. unbinary("00001000") would return 8. + */ + static final public int unbinary(String what) { + return Integer.parseInt(what, 2); + } + + + + ////////////////////////////////////////////////////////////// + + // COLOR FUNCTIONS + + // moved here so that they can work without + // the graphics actually being instantiated (outside setup) + + + public final int color(int gray) { + if (g == null) { + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + return 0xff000000 | (gray << 16) | (gray << 8) | gray; + } + return g.color(gray); + } + + + public final int color(float fgray) { + if (g == null) { + int gray = (int) fgray; + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + return 0xff000000 | (gray << 16) | (gray << 8) | gray; + } + return g.color(fgray); + } + + + /** + * As of 0116 this also takes color(#FF8800, alpha) + */ + public final int color(int gray, int alpha) { + if (g == null) { + if (alpha > 255) alpha = 255; else if (alpha < 0) alpha = 0; + if (gray > 255) { + // then assume this is actually a #FF8800 + return (alpha << 24) | (gray & 0xFFFFFF); + } else { + //if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + return (alpha << 24) | (gray << 16) | (gray << 8) | gray; + } + } + return g.color(gray, alpha); + } + + + public final int color(float fgray, float falpha) { + if (g == null) { + int gray = (int) fgray; + int alpha = (int) falpha; + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + if (alpha > 255) alpha = 255; else if (alpha < 0) alpha = 0; + return 0xff000000 | (gray << 16) | (gray << 8) | gray; + } + return g.color(fgray, falpha); + } + + + public final int color(int x, int y, int z) { + if (g == null) { + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return 0xff000000 | (x << 16) | (y << 8) | z; + } + return g.color(x, y, z); + } + + + public final int color(float x, float y, float z) { + if (g == null) { + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return 0xff000000 | ((int)x << 16) | ((int)y << 8) | (int)z; + } + return g.color(x, y, z); + } + + + public final int color(int x, int y, int z, int a) { + if (g == null) { + if (a > 255) a = 255; else if (a < 0) a = 0; + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return (a << 24) | (x << 16) | (y << 8) | z; + } + return g.color(x, y, z, a); + } + + + public final int color(float x, float y, float z, float a) { + if (g == null) { + if (a > 255) a = 255; else if (a < 0) a = 0; + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return ((int)a << 24) | ((int)x << 16) | ((int)y << 8) | (int)z; + } + return g.color(x, y, z, a); + } + + + + ////////////////////////////////////////////////////////////// + + // MAIN + + + private static class WorkerVar { + private Thread thread; + WorkerVar(Thread t) { thread = t; } + synchronized Thread get() { return thread; } + synchronized void clear() { thread = null; } + } + + /** + * Class to help external communication run as a separate class. + *

+ * From a software engineering standpoint, using the stderr stream + * is highly problematic because of its tendency to die or act + * funny, especially on Windows. Threading issues can cause the + * buffers to get full or the applet to not run properly. + * Formerly known as the "code folder bug", this has been fixed + * through the use of this class, however it remains a tenuous + * situation that could perhaps break in a future JDK release. + */ + class Worker { + private Object value; + private WorkerVar workerVar; + + protected synchronized Object getValue() { + return value; + } + + private synchronized void setValue(Object x) { + value = x; + } + + public Object construct() { + try { + int anything = System.in.read(); + if (anything == EXTERNAL_STOP) { + + // adding this for 0073.. need to stop libraries + // when the stop button is hit. + PApplet.this.stop(); + finished = true; + } + } catch (IOException e) { + finished = true; + } + try { + Thread.sleep(250); + //Thread.sleep(100); // kick up latency for 0075? + } catch (InterruptedException e) { } + return null; + } + + // removing this from SwingWorker + //public void finished() { } + + public void interrupt() { + Thread t = workerVar.get(); + if (t != null) { + t.interrupt(); + } + workerVar.clear(); + } + + public Object get() { + while (true) { + Thread t = workerVar.get(); + if (t == null) { + return getValue(); + } + try { + t.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // propagate + return null; + } + } + } + + public Worker() { + // removing this from SwingWorker + //final Runnable doFinished = new Runnable() { + // public void run() { finished(); } + // }; + + Runnable doConstruct = new Runnable() { + public void run() { + try { + setValue(construct()); + + } finally { + workerVar.clear(); + } + // removing this from SwingWorker to avoid swing + //javax.swing.SwingUtilities.invokeLater(doFinished); + } + }; + + Thread t = new Thread(doConstruct); + workerVar = new WorkerVar(t); + } + + public void start() { + Thread t = workerVar.get(); + if (t != null) t.start(); + } + } + + + /** + * Set this sketch to communicate its state back to the PDE. + *

+ * This uses the stderr stream to write positions of the window + * (so that it will be saved by the PDE for the next run) and + * notify on quit. See more notes in the Worker class. + */ + public void setupExternalMessages() { //Frame parentFrame) { + //final Worker worker = new Worker(); + + //parentFrame.addComponentListener(new ComponentAdapter() { + frame.addComponentListener(new ComponentAdapter() { + public void componentMoved(ComponentEvent e) { + Point where = ((Frame) e.getSource()).getLocation(); + System.err.println(PApplet.EXTERNAL_MOVE + " " + + where.x + " " + where.y); + System.err.flush(); // doesn't seem to help or hurt + } + }); + + //parentFrame.addWindowListener(new WindowAdapter() { + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.err.println(PApplet.EXTERNAL_QUIT); + System.err.flush(); // important + System.exit(0); + } + }); + } + + + /** + * Set up a listener that will fire proper component resize events + * in cases where frame.setResizable(true) is called. + */ + public void setupFrameResizeListener() { + frame.addComponentListener(new ComponentAdapter() { + + public void componentResized(ComponentEvent e) { + // Ignore bad resize events fired during setup to fix + // http://dev.processing.org/bugs/show_bug.cgi?id=341 + // This should also fix the blank screen on Linux bug + // http://dev.processing.org/bugs/show_bug.cgi?id=282 + if (frame.isResizable()) { + // might be multiple resize calls before visible (i.e. first + // when pack() is called, then when it's resized for use). + // ignore them because it's not the user resizing things. + Frame farm = (Frame) e.getComponent(); + if (farm.isVisible()) { + Insets insets = farm.getInsets(); + Dimension windowSize = farm.getSize(); + int usableW = windowSize.width - insets.left - insets.right; + int usableH = windowSize.height - insets.top - insets.bottom; + + // the ComponentListener in PApplet will handle calling size() + setBounds(insets.left, insets.top, usableW, usableH); + } + } + } + }); + } + + + /** + * main() method for running this class from the command line. + *

+ * The options shown here are not yet finalized and will be + * changing over the next several releases. + *

+ * The simplest way to turn and applet into an application is to + * add the following code to your program: + *

static public void main(String args[]) {
+   *   PApplet.main(new String[] { "YourSketchName" });
+   * }
+ * This will properly launch your applet from a double-clickable + * .jar or from the command line. + *
+   * Parameters useful for launching or also used by the PDE:
+   *
+   * --location=x,y        upper-lefthand corner of where the applet
+   *                       should appear on screen. if not used,
+   *                       the default is to center on the main screen.
+   *
+   * --present             put the applet into full screen presentation
+   *                       mode. requires java 1.4 or later.
+   *
+   * --hide-stop           use to hide the stop button in situations where
+   *                       you don't want to allow users to exit. also
+   *                       see the FAQ on information for capturing the ESC
+   *                       key when running in presentation mode.
+   *
+   * --stop-color          color of the 'stop' text used to quit an
+   *                       sketch when it's in present mode.
+   *
+   * --bgcolor=#xxxxxx     background color of the window.
+   *
+   * --sketch-path         location of where to save files from functions
+   *                       like saveStrings() or saveFrame(). defaults to
+   *                       the folder that the java application was
+   *                       launched from, which means if this isn't set by
+   *                       the pde, everything goes into the same folder
+   *                       as processing.exe.
+   *
+   * --display=n           set what display should be used by this applet.
+   *                       displays are numbered starting from 1.
+   *
+   *
+   * Parameters used by Processing when running via the PDE
+   *
+   * --external            set when the applet is being used by the PDE
+   *
+   * --editor-location=x,y position of the upper-lefthand corner of the
+   *                       editor window, for placement of applet window
+   * 
+ */ + static public void main(String args[]) { + if (args.length < 1) { + System.err.println("Usage: PApplet "); + System.err.println("For additional options, " + + "see the javadoc for PApplet"); + System.exit(1); + } + + try { + // true if this sketch is being run by the PDE + boolean external = false; + int location[] = null; + int editorLocation[] = null; + + String name = null; + boolean present = false; + Color backgroundColor = Color.black; //BLACK; + Color stopColor = Color.gray; //GRAY; + GraphicsDevice displayDevice = null; + boolean hideStop = false; + + String param = null, value = null; + + // try to get the user folder. if running under java web start, + // this may cause a security exception if the code is not signed. + // http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Integrate;action=display;num=1159386274 + String folder = null; + try { + folder = System.getProperty("user.dir"); + } catch (Exception e) { } + + int argIndex = 0; + while (argIndex < args.length) { + int equals = args[argIndex].indexOf('='); + if (equals != -1) { + param = args[argIndex].substring(0, equals); + value = args[argIndex].substring(equals + 1); + + if (param.equals(ARGS_EDITOR_LOCATION)) { + external = true; + editorLocation = parseInt(split(value, ',')); + + } else if (param.equals(ARGS_DISPLAY)) { + int deviceIndex = Integer.parseInt(value) - 1; + + //DisplayMode dm = device.getDisplayMode(); + //if ((dm.getWidth() == 1024) && (dm.getHeight() == 768)) { + + GraphicsEnvironment environment = + GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice devices[] = environment.getScreenDevices(); + if ((deviceIndex >= 0) && (deviceIndex < devices.length)) { + displayDevice = devices[deviceIndex]; + } else { + System.err.println("Display " + value + " does not exist, " + + "using the default display instead."); + } + + } else if (param.equals(ARGS_BGCOLOR)) { + if (value.charAt(0) == '#') value = value.substring(1); + backgroundColor = new Color(Integer.parseInt(value, 16)); + + } else if (param.equals(ARGS_STOP_COLOR)) { + if (value.charAt(0) == '#') value = value.substring(1); + stopColor = new Color(Integer.parseInt(value, 16)); + + } else if (param.equals(ARGS_SKETCH_FOLDER)) { + folder = value; + + } else if (param.equals(ARGS_LOCATION)) { + location = parseInt(split(value, ',')); + } + + } else { + if (args[argIndex].equals(ARGS_PRESENT)) { + present = true; + + } else if (args[argIndex].equals(ARGS_HIDE_STOP)) { + hideStop = true; + + } else if (args[argIndex].equals(ARGS_EXTERNAL)) { + external = true; + + } else { + name = args[argIndex]; + break; + } + } + argIndex++; + } + + if (displayDevice == null) { + GraphicsEnvironment environment = + GraphicsEnvironment.getLocalGraphicsEnvironment(); + displayDevice = environment.getDefaultScreenDevice(); + } + + Frame frame = new Frame(displayDevice.getDefaultConfiguration()); + /* + Frame frame = null; + if (displayDevice != null) { + frame = new Frame(displayDevice.getDefaultConfiguration()); + } else { + frame = new Frame(); + } + */ + + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + // remove the grow box by default + // users who want it back can call frame.setResizable(true) + frame.setResizable(false); + + Class c = Class.forName(name); + PApplet applet = (PApplet) c.newInstance(); + + // these are needed before init/start + applet.frame = frame; + applet.sketchPath = folder; + applet.args = PApplet.subset(args, 1); + + applet.init(); + + // wait until the applet has figured out its width + // hoping that this won't hang if the applet has an exception + while (applet.defaultSize && !applet.finished) { + try { + Thread.sleep(5); + + } catch (InterruptedException e) { } + } + + if (present) { + frame.setUndecorated(true); + frame.setBackground(backgroundColor); + displayDevice.setFullScreenWindow(frame); + + frame.add(applet); + Dimension fullscreen = frame.getSize(); + applet.setBounds((fullscreen.width - applet.width) / 2, + (fullscreen.height - applet.height) / 2, + applet.width, applet.height); + + if (!hideStop) { + Label label = new Label("stop"); + label.setForeground(stopColor); + label.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + System.exit(0); + } + }); + frame.add(label); + + Dimension labelSize = label.getPreferredSize(); + // sometimes shows up truncated on mac + //System.out.println("label width is " + labelSize.width); + labelSize = new Dimension(100, labelSize.height); + label.setSize(labelSize); + label.setLocation(20, fullscreen.height - labelSize.height - 20); + } + + // not always running externally when in present mode + if (external) { + applet.setupExternalMessages(); + } + + } else { // if not presenting + // can't do pack earlier cuz present mode don't like it + // (can't go full screen with a frame after calling pack) + frame.pack(); // get insets. get more. + Insets insets = frame.getInsets(); + + int windowW = Math.max(applet.width, MIN_WINDOW_WIDTH) + + insets.left + insets.right; + int windowH = Math.max(applet.height, MIN_WINDOW_HEIGHT) + + insets.top + insets.bottom; + + frame.setSize(windowW, windowH); + + if (location != null) { + // a specific location was received from PdeRuntime + // (applet has been run more than once, user placed window) + frame.setLocation(location[0], location[1]); + + } else if (external) { + int locationX = editorLocation[0] - 20; + int locationY = editorLocation[1]; + + if (locationX - windowW > 10) { + // if it fits to the left of the window + frame.setLocation(locationX - windowW, locationY); + + } else { // doesn't fit + // if it fits inside the editor window, + // offset slightly from upper lefthand corner + // so that it's plunked inside the text area + locationX = editorLocation[0] + 66; + locationY = editorLocation[1] + 66; + + if ((locationX + windowW > screen.width - 33) || + (locationY + windowH > screen.height - 33)) { + // otherwise center on screen + locationX = (screen.width - windowW) / 2; + locationY = (screen.height - windowH) / 2; + } + frame.setLocation(locationX, locationY); + } + } else { // just center on screen + frame.setLocation((screen.width - applet.width) / 2, + (screen.height - applet.height) / 2); + } + + frame.setLayout(null); + frame.add(applet); + + if (backgroundColor == Color.black) { //BLACK) { + // this means no bg color unless specified + backgroundColor = SystemColor.control; + } + frame.setBackground(backgroundColor); + + int usableWindowH = windowH - insets.top - insets.bottom; + applet.setBounds((windowW - applet.width)/2, + insets.top + (usableWindowH - applet.height)/2, + applet.width, applet.height); + + if (external) { + applet.setupExternalMessages(); + + } else { // !external + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + } + + // handle frame resizing events + applet.setupFrameResizeListener(); + + // all set for rockin + if (applet.displayable()) { + frame.setVisible(true); + } + } + + //System.out.println("showing frame"); + //System.out.println("applet requesting focus"); + applet.requestFocus(); // ask for keydowns + //System.out.println("exiting main()"); + + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + + + ////////////////////////////////////////////////////////////// + + + public PGraphics beginRecord(String renderer, String filename) { + filename = insertFrame(filename); + //filename = savePath(filename); + PGraphics rec = createGraphics(width, height, renderer, filename); + beginRecord(rec); + return rec; + } + + + public void beginRecord(PGraphics recorder) { + this.recorder = recorder; + recorder.beginDraw(); + } + + + public void endRecord() { + //println("endRecord()"); + //if (!recorderNull) { + if (recorder != null) { + //recorder.endRecord(); + recorder.endDraw(); + recorder.dispose(); + recorder = null; + } + } + + + public PGraphics beginRaw(String renderer, String filename) { + filename = insertFrame(filename); + //filename = savePath(filename); // ensure an absolute path + PGraphics rec = createGraphics(width, height, renderer, filename); + //g.recordRaw(rec); + g.beginRaw(rec); + return rec; + } + + + /** + * Check a string for #### signs to see if the frame number should be + * inserted. Used for functions like saveFrame() and beginRecord() to + * replace the # marks with the frame number. If only one # is used, + * it will be ignored, under the assumption that it's probably not + * intended to be the frame number. + */ + public String insertFrame(String what) { + int first = what.indexOf('#'); + int last = what.lastIndexOf('#'); + + if ((first != -1) && (last - first > 0)) { + String prefix = what.substring(0, first); + int count = last - first + 1; + String suffix = what.substring(last + 1); + return prefix + nf(frameCount, count) + suffix; + } + return what; // no change + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Override the g.pixels[] function to set the pixels[] array + * that's part of the PApplet object. Allows the use of + * pixels[] in the code, rather than g.pixels[]. + */ + public void loadPixels() { + g.loadPixels(); + pixels = g.pixels; + } + + + ////////////////////////////////////////////////////////////// + + // everything below this line is automatically generated. no touch. + // public functions for processing.core + + + public void imageMode(int mode) { + if (recorder != null) recorder.imageMode(mode); + g.imageMode(mode); + } + + + public void smooth() { + if (recorder != null) recorder.smooth(); + g.smooth(); + } + + + public void noSmooth() { + if (recorder != null) recorder.noSmooth(); + g.noSmooth(); + } + + + public void updatePixels() { + if (recorder != null) recorder.updatePixels(); + g.updatePixels(); + } + + + public void updatePixels(int x1, int y1, int x2, int y2) { + if (recorder != null) recorder.updatePixels(x1, y1, x2, y2); + g.updatePixels(x1, y1, x2, y2); + } + + + public int get(int x, int y) { + return g.get(x, y); + } + + + public PImage get(int x, int y, int w, int h) { + return g.get(x, y, w, h); + } + + + public PImage get() { + return g.get(); + } + + + public void set(int x, int y, int c) { + if (recorder != null) recorder.set(x, y, c); + g.set(x, y, c); + } + + + public void set(int dx, int dy, PImage src) { + if (recorder != null) recorder.set(dx, dy, src); + g.set(dx, dy, src); + } + + + public void mask(int alpha[]) { + if (recorder != null) recorder.mask(alpha); + g.mask(alpha); + } + + + public void mask(PImage alpha) { + if (recorder != null) recorder.mask(alpha); + g.mask(alpha); + } + + + public void filter(int kind) { + if (recorder != null) recorder.filter(kind); + g.filter(kind); + } + + + public void filter(int kind, float param) { + if (recorder != null) recorder.filter(kind, param); + g.filter(kind, param); + } + + + public void copy(int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2) { + if (recorder != null) recorder.copy(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); + g.copy(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); + } + + + public void copy(PImage src, + int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2) { + if (recorder != null) recorder.copy(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); + g.copy(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); + } + + + static public int blendColor(int c1, int c2, int mode) { + return PGraphics.blendColor(c1, c2, mode); + } + + + public void blend(int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2, int mode) { + if (recorder != null) recorder.blend(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + g.blend(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + } + + + public void blend(PImage src, + int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2, int mode) { + if (recorder != null) recorder.blend(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + g.blend(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + } + + + public void hint(int which) { + if (recorder != null) recorder.hint(which); + g.hint(which); + } + + + public void noHint(int which) { + if (recorder != null) recorder.noHint(which); + g.noHint(which); + } + + + public void beginShape() { + if (recorder != null) recorder.beginShape(); + g.beginShape(); + } + + + public void beginShape(int kind) { + if (recorder != null) recorder.beginShape(kind); + g.beginShape(kind); + } + + + public void normal(float nx, float ny, float nz) { + if (recorder != null) recorder.normal(nx, ny, nz); + g.normal(nx, ny, nz); + } + + + public void textureMode(int mode) { + if (recorder != null) recorder.textureMode(mode); + g.textureMode(mode); + } + + + public void texture(PImage image) { + if (recorder != null) recorder.texture(image); + g.texture(image); + } + + + public void vertex(float x, float y) { + if (recorder != null) recorder.vertex(x, y); + g.vertex(x, y); + } + + + public void vertex(float x, float y, float z) { + if (recorder != null) recorder.vertex(x, y, z); + g.vertex(x, y, z); + } + + + public void vertex(float x, float y, float u, float v) { + if (recorder != null) recorder.vertex(x, y, u, v); + g.vertex(x, y, u, v); + } + + + public void vertex(float x, float y, float z, float u, float v) { + if (recorder != null) recorder.vertex(x, y, z, u, v); + g.vertex(x, y, z, u, v); + } + + + public void bezierVertex(float x2, float y2, + float x3, float y3, + float x4, float y4) { + if (recorder != null) recorder.bezierVertex(x2, y2, x3, y3, x4, y4); + g.bezierVertex(x2, y2, x3, y3, x4, y4); + } + + + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + if (recorder != null) recorder.bezierVertex(x2, y2, z2, x3, y3, z3, x4, y4, z4); + g.bezierVertex(x2, y2, z2, x3, y3, z3, x4, y4, z4); + } + + + public void curveVertex(float x, float y) { + if (recorder != null) recorder.curveVertex(x, y); + g.curveVertex(x, y); + } + + + public void curveVertex(float x, float y, float z) { + if (recorder != null) recorder.curveVertex(x, y, z); + g.curveVertex(x, y, z); + } + + + public void breakShape() { + if (recorder != null) recorder.breakShape(); + g.breakShape(); + } + + + public final void endShape() { + if (recorder != null) recorder.endShape(); + g.endShape(); + } + + + public void endShape(int mode) { + if (recorder != null) recorder.endShape(mode); + g.endShape(mode); + } + + + public void point(float x, float y) { + if (recorder != null) recorder.point(x, y); + g.point(x, y); + } + + + public void point(float x, float y, float z) { + if (recorder != null) recorder.point(x, y, z); + g.point(x, y, z); + } + + + public void line(float x1, float y1, float x2, float y2) { + if (recorder != null) recorder.line(x1, y1, x2, y2); + g.line(x1, y1, x2, y2); + } + + + public void line(float x1, float y1, float z1, + float x2, float y2, float z2) { + if (recorder != null) recorder.line(x1, y1, z1, x2, y2, z2); + g.line(x1, y1, z1, x2, y2, z2); + } + + + public void triangle(float x1, float y1, float x2, float y2, + float x3, float y3) { + if (recorder != null) recorder.triangle(x1, y1, x2, y2, x3, y3); + g.triangle(x1, y1, x2, y2, x3, y3); + } + + + public void quad(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + if (recorder != null) recorder.quad(x1, y1, x2, y2, x3, y3, x4, y4); + g.quad(x1, y1, x2, y2, x3, y3, x4, y4); + } + + + public void rectMode(int mode) { + if (recorder != null) recorder.rectMode(mode); + g.rectMode(mode); + } + + + public void rect(float x1, float y1, float x2, float y2) { + if (recorder != null) recorder.rect(x1, y1, x2, y2); + g.rect(x1, y1, x2, y2); + } + + + public void ellipseMode(int mode) { + if (recorder != null) recorder.ellipseMode(mode); + g.ellipseMode(mode); + } + + + public void ellipse(float a, float b, float c, float d) { + if (recorder != null) recorder.ellipse(a, b, c, d); + g.ellipse(a, b, c, d); + } + + + public void arc(float a, float b, float c, float d, + float start, float stop) { + if (recorder != null) recorder.arc(a, b, c, d, start, stop); + g.arc(a, b, c, d, start, stop); + } + + + public void box(float size) { + if (recorder != null) recorder.box(size); + g.box(size); + } + + + public void box(float w, float h, float d) { + if (recorder != null) recorder.box(w, h, d); + g.box(w, h, d); + } + + + public void sphereDetail(int res) { + if (recorder != null) recorder.sphereDetail(res); + g.sphereDetail(res); + } + + + public void sphere(float r) { + if (recorder != null) recorder.sphere(r); + g.sphere(r); + } + + + public float bezierPoint(float a, float b, float c, float d, float t) { + return g.bezierPoint(a, b, c, d, t); + } + + + public float bezierTangent(float a, float b, float c, float d, float t) { + return g.bezierTangent(a, b, c, d, t); + } + + + public void bezierDetail(int detail) { + if (recorder != null) recorder.bezierDetail(detail); + g.bezierDetail(detail); + } + + + public void bezier(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + if (recorder != null) recorder.bezier(x1, y1, x2, y2, x3, y3, x4, y4); + g.bezier(x1, y1, x2, y2, x3, y3, x4, y4); + } + + + public void bezier(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + if (recorder != null) recorder.bezier(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); + g.bezier(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); + } + + + public float curvePoint(float a, float b, float c, float d, float t) { + return g.curvePoint(a, b, c, d, t); + } + + + public float curveTangent(float a, float b, float c, float d, + float t) { + return g.curveTangent(a, b, c, d, t); + } + + + public void curveDetail(int detail) { + if (recorder != null) recorder.curveDetail(detail); + g.curveDetail(detail); + } + + + public void curveTightness(float tightness) { + if (recorder != null) recorder.curveTightness(tightness); + g.curveTightness(tightness); + } + + + public void curve(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + if (recorder != null) recorder.curve(x1, y1, x2, y2, x3, y3, x4, y4); + g.curve(x1, y1, x2, y2, x3, y3, x4, y4); + } + + + public void curve(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + if (recorder != null) recorder.curve(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); + g.curve(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); + } + + + public void image(PImage image, float x, float y) { + if (recorder != null) recorder.image(image, x, y); + g.image(image, x, y); + } + + + public void image(PImage image, + float x, float y, float c, float d) { + if (recorder != null) recorder.image(image, x, y, c, d); + g.image(image, x, y, c, d); + } + + + public void image(PImage image, + float a, float b, float c, float d, + int u1, int v1, int u2, int v2) { + if (recorder != null) recorder.image(image, a, b, c, d, u1, v1, u2, v2); + g.image(image, a, b, c, d, u1, v1, u2, v2); + } + + + public void textAlign(int align) { + if (recorder != null) recorder.textAlign(align); + g.textAlign(align); + } + + + public void textAlign(int alignX, int alignY) { + if (recorder != null) recorder.textAlign(alignX, alignY); + g.textAlign(alignX, alignY); + } + + + public float textAscent() { + return g.textAscent(); + } + + + public float textDescent() { + return g.textDescent(); + } + + + public void textFont(PFont which) { + if (recorder != null) recorder.textFont(which); + g.textFont(which); + } + + + public void textFont(PFont which, float size) { + if (recorder != null) recorder.textFont(which, size); + g.textFont(which, size); + } + + + public void textLeading(float leading) { + if (recorder != null) recorder.textLeading(leading); + g.textLeading(leading); + } + + + public void textMode(int mode) { + if (recorder != null) recorder.textMode(mode); + g.textMode(mode); + } + + + public void textSize(float size) { + if (recorder != null) recorder.textSize(size); + g.textSize(size); + } + + + public float textWidth(char c) { + return g.textWidth(c); + } + + + public float textWidth(String str) { + return g.textWidth(str); + } + + + public void text(char c) { + if (recorder != null) recorder.text(c); + g.text(c); + } + + + public void text(char c, float x, float y) { + if (recorder != null) recorder.text(c, x, y); + g.text(c, x, y); + } + + + public void text(char c, float x, float y, float z) { + if (recorder != null) recorder.text(c, x, y, z); + g.text(c, x, y, z); + } + + + public void text(String str) { + if (recorder != null) recorder.text(str); + g.text(str); + } + + + public void text(String str, float x, float y) { + if (recorder != null) recorder.text(str, x, y); + g.text(str, x, y); + } + + + public void text(String str, float x, float y, float z) { + if (recorder != null) recorder.text(str, x, y, z); + g.text(str, x, y, z); + } + + + public void text(String str, float x1, float y1, float x2, float y2) { + if (recorder != null) recorder.text(str, x1, y1, x2, y2); + g.text(str, x1, y1, x2, y2); + } + + + public void text(String s, float x1, float y1, float x2, float y2, float z) { + if (recorder != null) recorder.text(s, x1, y1, x2, y2, z); + g.text(s, x1, y1, x2, y2, z); + } + + + public void text(int num, float x, float y) { + if (recorder != null) recorder.text(num, x, y); + g.text(num, x, y); + } + + + public void text(int num, float x, float y, float z) { + if (recorder != null) recorder.text(num, x, y, z); + g.text(num, x, y, z); + } + + + public void text(float num, float x, float y) { + if (recorder != null) recorder.text(num, x, y); + g.text(num, x, y); + } + + + public void text(float num, float x, float y, float z) { + if (recorder != null) recorder.text(num, x, y, z); + g.text(num, x, y, z); + } + + + public void translate(float tx, float ty) { + if (recorder != null) recorder.translate(tx, ty); + g.translate(tx, ty); + } + + + public void translate(float tx, float ty, float tz) { + if (recorder != null) recorder.translate(tx, ty, tz); + g.translate(tx, ty, tz); + } + + + public void rotate(float angle) { + if (recorder != null) recorder.rotate(angle); + g.rotate(angle); + } + + + public void rotateX(float angle) { + if (recorder != null) recorder.rotateX(angle); + g.rotateX(angle); + } + + + public void rotateY(float angle) { + if (recorder != null) recorder.rotateY(angle); + g.rotateY(angle); + } + + + public void rotateZ(float angle) { + if (recorder != null) recorder.rotateZ(angle); + g.rotateZ(angle); + } + + + public void rotate(float angle, float vx, float vy, float vz) { + if (recorder != null) recorder.rotate(angle, vx, vy, vz); + g.rotate(angle, vx, vy, vz); + } + + + public void scale(float s) { + if (recorder != null) recorder.scale(s); + g.scale(s); + } + + + public void scale(float sx, float sy) { + if (recorder != null) recorder.scale(sx, sy); + g.scale(sx, sy); + } + + + public void scale(float x, float y, float z) { + if (recorder != null) recorder.scale(x, y, z); + g.scale(x, y, z); + } + + + public void pushMatrix() { + if (recorder != null) recorder.pushMatrix(); + g.pushMatrix(); + } + + + public void popMatrix() { + if (recorder != null) recorder.popMatrix(); + g.popMatrix(); + } + + + public void resetMatrix() { + if (recorder != null) recorder.resetMatrix(); + g.resetMatrix(); + } + + + public void applyMatrix(float n00, float n01, float n02, + float n10, float n11, float n12) { + if (recorder != null) recorder.applyMatrix(n00, n01, n02, n10, n11, n12); + g.applyMatrix(n00, n01, n02, n10, n11, n12); + } + + + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + if (recorder != null) recorder.applyMatrix(n00, n01, n02, n03, n10, n11, n12, n13, n20, n21, n22, n23, n30, n31, n32, n33); + g.applyMatrix(n00, n01, n02, n03, n10, n11, n12, n13, n20, n21, n22, n23, n30, n31, n32, n33); + } + + + public void loadMatrix() { + if (recorder != null) recorder.loadMatrix(); + g.loadMatrix(); + } + + + public void printMatrix() { + if (recorder != null) recorder.printMatrix(); + g.printMatrix(); + } + + + public void beginCamera() { + if (recorder != null) recorder.beginCamera(); + g.beginCamera(); + } + + + public void endCamera() { + if (recorder != null) recorder.endCamera(); + g.endCamera(); + } + + + public void camera() { + if (recorder != null) recorder.camera(); + g.camera(); + } + + + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + if (recorder != null) recorder.camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); + g.camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); + } + + + public void printCamera() { + if (recorder != null) recorder.printCamera(); + g.printCamera(); + } + + + public void ortho() { + if (recorder != null) recorder.ortho(); + g.ortho(); + } + + + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + if (recorder != null) recorder.ortho(left, right, bottom, top, near, far); + g.ortho(left, right, bottom, top, near, far); + } + + + public void perspective() { + if (recorder != null) recorder.perspective(); + g.perspective(); + } + + + public void perspective(float fovy, float aspect, float zNear, float zFar) { + if (recorder != null) recorder.perspective(fovy, aspect, zNear, zFar); + g.perspective(fovy, aspect, zNear, zFar); + } + + + public void frustum(float left, float right, float bottom, + float top, float znear, float zfar) { + if (recorder != null) recorder.frustum(left, right, bottom, top, znear, zfar); + g.frustum(left, right, bottom, top, znear, zfar); + } + + + public void printProjection() { + if (recorder != null) recorder.printProjection(); + g.printProjection(); + } + + + public float screenX(float x, float y) { + return g.screenX(x, y); + } + + + public float screenY(float x, float y) { + return g.screenY(x, y); + } + + + public float screenX(float x, float y, float z) { + return g.screenX(x, y, z); + } + + + public float screenY(float x, float y, float z) { + return g.screenY(x, y, z); + } + + + public float screenZ(float x, float y, float z) { + return g.screenZ(x, y, z); + } + + + public float modelX(float x, float y, float z) { + return g.modelX(x, y, z); + } + + + public float modelY(float x, float y, float z) { + return g.modelY(x, y, z); + } + + + public float modelZ(float x, float y, float z) { + return g.modelZ(x, y, z); + } + + + public void colorMode(int mode) { + if (recorder != null) recorder.colorMode(mode); + g.colorMode(mode); + } + + + public void colorMode(int mode, float max) { + if (recorder != null) recorder.colorMode(mode, max); + g.colorMode(mode, max); + } + + + public void colorMode(int mode, + float maxX, float maxY, float maxZ) { + if (recorder != null) recorder.colorMode(mode, maxX, maxY, maxZ); + g.colorMode(mode, maxX, maxY, maxZ); + } + + + public void colorMode(int mode, + float maxX, float maxY, float maxZ, float maxA) { + if (recorder != null) recorder.colorMode(mode, maxX, maxY, maxZ, maxA); + g.colorMode(mode, maxX, maxY, maxZ, maxA); + } + + + public void strokeWeight(float weight) { + if (recorder != null) recorder.strokeWeight(weight); + g.strokeWeight(weight); + } + + + public void strokeJoin(int join) { + if (recorder != null) recorder.strokeJoin(join); + g.strokeJoin(join); + } + + + public void strokeCap(int cap) { + if (recorder != null) recorder.strokeCap(cap); + g.strokeCap(cap); + } + + + public void noStroke() { + if (recorder != null) recorder.noStroke(); + g.noStroke(); + } + + + public void stroke(int rgb) { + if (recorder != null) recorder.stroke(rgb); + g.stroke(rgb); + } + + + public void stroke(int rgb, float alpha) { + if (recorder != null) recorder.stroke(rgb, alpha); + g.stroke(rgb, alpha); + } + + + public void stroke(float gray) { + if (recorder != null) recorder.stroke(gray); + g.stroke(gray); + } + + + public void stroke(float gray, float alpha) { + if (recorder != null) recorder.stroke(gray, alpha); + g.stroke(gray, alpha); + } + + + public void stroke(float x, float y, float z) { + if (recorder != null) recorder.stroke(x, y, z); + g.stroke(x, y, z); + } + + + public void stroke(float x, float y, float z, float a) { + if (recorder != null) recorder.stroke(x, y, z, a); + g.stroke(x, y, z, a); + } + + + public void noTint() { + if (recorder != null) recorder.noTint(); + g.noTint(); + } + + + public void tint(int rgb) { + if (recorder != null) recorder.tint(rgb); + g.tint(rgb); + } + + + public void tint(int rgb, float alpha) { + if (recorder != null) recorder.tint(rgb, alpha); + g.tint(rgb, alpha); + } + + + public void tint(float gray) { + if (recorder != null) recorder.tint(gray); + g.tint(gray); + } + + + public void tint(float gray, float alpha) { + if (recorder != null) recorder.tint(gray, alpha); + g.tint(gray, alpha); + } + + + public void tint(float x, float y, float z) { + if (recorder != null) recorder.tint(x, y, z); + g.tint(x, y, z); + } + + + public void tint(float x, float y, float z, float a) { + if (recorder != null) recorder.tint(x, y, z, a); + g.tint(x, y, z, a); + } + + + public void noFill() { + if (recorder != null) recorder.noFill(); + g.noFill(); + } + + + public void fill(int rgb) { + if (recorder != null) recorder.fill(rgb); + g.fill(rgb); + } + + + public void fill(int rgb, float alpha) { + if (recorder != null) recorder.fill(rgb, alpha); + g.fill(rgb, alpha); + } + + + public void fill(float gray) { + if (recorder != null) recorder.fill(gray); + g.fill(gray); + } + + + public void fill(float gray, float alpha) { + if (recorder != null) recorder.fill(gray, alpha); + g.fill(gray, alpha); + } + + + public void fill(float x, float y, float z) { + if (recorder != null) recorder.fill(x, y, z); + g.fill(x, y, z); + } + + + public void fill(float x, float y, float z, float a) { + if (recorder != null) recorder.fill(x, y, z, a); + g.fill(x, y, z, a); + } + + + public void ambient(int rgb) { + if (recorder != null) recorder.ambient(rgb); + g.ambient(rgb); + } + + + public void ambient(float gray) { + if (recorder != null) recorder.ambient(gray); + g.ambient(gray); + } + + + public void ambient(float x, float y, float z) { + if (recorder != null) recorder.ambient(x, y, z); + g.ambient(x, y, z); + } + + + public void specular(int rgb) { + if (recorder != null) recorder.specular(rgb); + g.specular(rgb); + } + + + public void specular(float gray) { + if (recorder != null) recorder.specular(gray); + g.specular(gray); + } + + + public void specular(float gray, float alpha) { + if (recorder != null) recorder.specular(gray, alpha); + g.specular(gray, alpha); + } + + + public void specular(float x, float y, float z) { + if (recorder != null) recorder.specular(x, y, z); + g.specular(x, y, z); + } + + + public void specular(float x, float y, float z, float a) { + if (recorder != null) recorder.specular(x, y, z, a); + g.specular(x, y, z, a); + } + + + public void shininess(float shine) { + if (recorder != null) recorder.shininess(shine); + g.shininess(shine); + } + + + public void emissive(int rgb) { + if (recorder != null) recorder.emissive(rgb); + g.emissive(rgb); + } + + + public void emissive(float gray) { + if (recorder != null) recorder.emissive(gray); + g.emissive(gray); + } + + + public void emissive(float x, float y, float z ) { + if (recorder != null) recorder.emissive(x, y, z); + g.emissive(x, y, z); + } + + + public void lights() { + if (recorder != null) recorder.lights(); + g.lights(); + } + + + public void ambientLight(float red, float green, float blue) { + if (recorder != null) recorder.ambientLight(red, green, blue); + g.ambientLight(red, green, blue); + } + + + public void ambientLight(float red, float green, float blue, + float x, float y, float z) { + if (recorder != null) recorder.ambientLight(red, green, blue, x, y, z); + g.ambientLight(red, green, blue, x, y, z); + } + + + public void directionalLight(float red, float green, float blue, + float nx, float ny, float nz) { + if (recorder != null) recorder.directionalLight(red, green, blue, nx, ny, nz); + g.directionalLight(red, green, blue, nx, ny, nz); + } + + + public void pointLight(float red, float green, float blue, + float x, float y, float z) { + if (recorder != null) recorder.pointLight(red, green, blue, x, y, z); + g.pointLight(red, green, blue, x, y, z); + } + + + public void spotLight(float red, float green, float blue, + float x, float y, float z, + float nx, float ny, float nz, + float angle, float concentration) { + if (recorder != null) recorder.spotLight(red, green, blue, x, y, z, nx, ny, nz, angle, concentration); + g.spotLight(red, green, blue, x, y, z, nx, ny, nz, angle, concentration); + } + + + public void lightFalloff(float constant, float linear, float quadratic) { + if (recorder != null) recorder.lightFalloff(constant, linear, quadratic); + g.lightFalloff(constant, linear, quadratic); + } + + + public void lightSpecular(float x, float y, float z) { + if (recorder != null) recorder.lightSpecular(x, y, z); + g.lightSpecular(x, y, z); + } + + + public void background(int rgb) { + if (recorder != null) recorder.background(rgb); + g.background(rgb); + } + + + public void background(int rgb, float alpha) { + if (recorder != null) recorder.background(rgb, alpha); + g.background(rgb, alpha); + } + + + public void background(float gray) { + if (recorder != null) recorder.background(gray); + g.background(gray); + } + + + public void background(float gray, float alpha) { + if (recorder != null) recorder.background(gray, alpha); + g.background(gray, alpha); + } + + + public void background(float x, float y, float z) { + if (recorder != null) recorder.background(x, y, z); + g.background(x, y, z); + } + + + public void background(float x, float y, float z, float a) { + if (recorder != null) recorder.background(x, y, z, a); + g.background(x, y, z, a); + } + + + public void background(PImage image) { + if (recorder != null) recorder.background(image); + g.background(image); + } + + + public final float alpha(int what) { + return g.alpha(what); + } + + + public final float red(int what) { + return g.red(what); + } + + + public final float green(int what) { + return g.green(what); + } + + + public final float blue(int what) { + return g.blue(what); + } + + + public final float hue(int what) { + return g.hue(what); + } + + + public final float saturation(int what) { + return g.saturation(what); + } + + + public final float brightness(int what) { + return g.brightness(what); + } + + + public int lerpColor(int c1, int c2, float amt) { + return g.lerpColor(c1, c2, amt); + } + + + static public int lerpColor(int c1, int c2, float amt, int mode) { + return PGraphics.lerpColor(c1, c2, amt, mode); + } + + + public void beginRaw(PGraphics rawGraphics) { + if (recorder != null) recorder.beginRaw(rawGraphics); + g.beginRaw(rawGraphics); + } + + + public void endRaw() { + if (recorder != null) recorder.endRaw(); + g.endRaw(); + } + + + public boolean displayable() { + return g.displayable(); + } +} diff --git a/core/PConstants.java b/core/PConstants.java new file mode 100644 index 000000000..082794b2f --- /dev/null +++ b/core/PConstants.java @@ -0,0 +1,322 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.Cursor; +import java.awt.event.KeyEvent; + + +/** + * Numbers shared throughout processing.core. + *

+ * An attempt is made to keep the constants as short/non-verbose + * as possible. For instance, the constant is TIFF instead of + * FILE_TYPE_TIFF. We'll do this as long as we can get away with it. + */ +public interface PConstants { + + // renderers known to processing.core + + static final String P2D = "processing.core.PGraphics2D"; + static final String P3D = "processing.core.PGraphics3D"; + static final String JAVA2D = "processing.core.PGraphicsJava2D"; + static final String OPENGL = "processing.opengl.PGraphicsOpenGL"; + static final String PDF = "processing.pdf.PGraphicsPDF"; + static final String DXF = "processing.dxf.RawDXF"; + //static final String SVG = "processing.dxf.PGraphicsSVG"; + + + // platform IDs for PApplet.platform + + static final int WINDOWS = 1; + static final int MACOS9 = 2; + static final int MACOSX = 3; + static final int LINUX = 4; + static final int OTHER = 0; + + + // for better parity between c++ version (at no speed cost) + + static final float EPSILON = 0.0001f; + // was around for auto-port to mobile version + //static final float ONE = 1.0f; + + + // useful goodness + + static final float PI = (float) Math.PI; + static final float HALF_PI = PI / 2.0f; + static final float THIRD_PI = PI / 3.0f; + static final float QUARTER_PI = PI / 4.0f; + static final float TWO_PI = PI * 2.0f; + + static final float DEG_TO_RAD = PI/180.0f; + static final float RAD_TO_DEG = 180.0f/PI; + + + // angle modes + + //static final int RADIANS = 0; + //static final int DEGREES = 1; + + + // used by split, all the standard whitespace chars + // (also includes unicode nbsp, that little bostage) + + static final String WHITESPACE = " \t\n\r\f\u00A0"; + + + // for colors and/or images + + static final int RGB = 1; // image & color + static final int ARGB = 2; // image + static final int HSB = 3; // color + static final int ALPHA = 4; // image + + + // image file types + + static final int TIFF = 0; + static final int TARGA = 1; + static final int JPEG = 2; + static final int GIF = 3; + + + // filter/convert types + + static final int BLUR = 11; + static final int GRAY = 12; + static final int INVERT = 13; + static final int OPAQUE = 14; + static final int POSTERIZE = 15; + static final int THRESHOLD = 16; + static final int ERODE = 17; + static final int DILATE = 18; + + + // blend mode keyword definitions + // @see processing.core.PImage#blendColor(int,int,int) + + public final static int REPLACE = 0; + public final static int BLEND = 1 << 0; + public final static int ADD = 1 << 1; + public final static int SUBTRACT = 1 << 2; + public final static int LIGHTEST = 1 << 3; + public final static int DARKEST = 1 << 4; + public final static int DIFFERENCE = 1 << 5; + public final static int EXCLUSION = 1 << 6; + public final static int MULTIPLY = 1 << 7; + public final static int SCREEN = 1 << 8; + public final static int OVERLAY = 1 << 9; + public final static int HARD_LIGHT = 1 << 10; + public final static int SOFT_LIGHT = 1 << 11; + public final static int DODGE = 1 << 12; + public final static int BURN = 1 << 13; + + // colour component bitmasks + + public static final int ALPHA_MASK = 0xff000000; + public static final int RED_MASK = 0x00ff0000; + public static final int GREEN_MASK = 0x0000ff00; + public static final int BLUE_MASK = 0x000000ff; + + + // for messages + + static final int CHATTER = 0; + static final int COMPLAINT = 1; + static final int PROBLEM = 2; + + + // types of projection matrices + + static final int CUSTOM = 0; // user-specified fanciness + static final int ORTHOGRAPHIC = 2; // 2D isometric projection + static final int PERSPECTIVE = 3; // perspective matrix + + + // rendering settings + + static final float PIXEL_CENTER = 0.5f; // for polygon aa + + + // shapes + + // the low four bits set the variety, + // higher bits set the specific shape type + + static final int POINTS = (1 << 4) | 0; + + static final int LINES = (1 << 5) | 0; + //static final int LINE_STRIP = (1 << 5) | 1; + //static final int LINE_LOOP = (1 << 5) | 2; + + static final int TRIANGLES = (1 << 6) | 0; + static final int TRIANGLE_STRIP = (1 << 6) | 1; + static final int TRIANGLE_FAN = (1 << 6) | 2; + + static final int QUADS = (1 << 7) | 0; + static final int QUAD_STRIP = (1 << 7) | 1; + + static final int POLYGON = (1 << 8) | 0; + //static final int CONCAVE_POLYGON = (1 << 8) | 1; + //static final int CONVEX_POLYGON = (1 << 8) | 2; + + static final int OPEN = 1; + static final int CLOSE = 2; + + + // shape drawing modes + + /** Draw mode convention to use (x, y) to (width, height) */ + static final int CORNER = 0; + /** Draw mode convention to use (x1, y1) to (x2, y2) coordinates */ + static final int CORNERS = 1; + /** @deprecated Use RADIUS instead (as of 0125) */ + static final int CENTER_RADIUS = 2; + /** Draw mode from the center, and using the radius */ + static final int RADIUS = 2; + /** Draw from the center, using second pair of values as the diameter. + Formerly called CENTER_DIAMETER in alpha releases */ + static final int CENTER = 3; + + + // vertically alignment modes for text + + /** Default vertical alignment for text placement */ + static final int BASELINE = 0; + /** Align text to the top */ + static final int TOP = 101; + /** Align text from the bottom, using the baseline. */ + static final int BOTTOM = 102; + + + // uv texture orientation modes + + static final int NORMALIZED = 1; //_SPACE = 0; // 0..1 + static final int IMAGE = 2; + + + // text placement modes + + /** + * textMode(MODEL) is the default, meaning that characters + * will be affected by transformations like any other shapes. + *

+ * Changed value in 0093 to not interfere with LEFT, CENTER, and RIGHT. + */ + static final int MODEL = 4; + + /** + * textMode(SHAPE) draws text using the the glyph outlines of + * individual characters rather than as textures. If the outlines are + * not available, then textMode(SHAPE) will be ignored and textMode(MODEL) + * will be used instead. For this reason, be sure to call textMode() + * after calling textFont(). + *

+ * Currently, textMode(SHAPE) is only supported by OPENGL mode. + * It also requires Java 1.2 or higher (OPENGL requires 1.4 anyway) + */ + static final int SHAPE = 5; + + + // text alignment modes + // are inherited from LEFT, CENTER, RIGHT + + + // stroke modes + + static final int SQUARE = 1 << 0; // called 'butt' in the svg spec + static final int ROUND = 1 << 1; + static final int PROJECT = 1 << 2; // called 'square' in the svg spec + static final int MITER = 1 << 3; + static final int BEVEL = 1 << 5; + + + // lighting + + static final int AMBIENT = 0; + static final int DIRECTIONAL = 1; + static final int POINT = 2; + static final int SPOT = 3; + + + // key constants + + // only including the most-used of these guys + // if people need more esoteric keys, they can learn about + // the esoteric java KeyEvent api and of virtual keys + + // both key and keyCode will equal these values + // for 0125, these were changed to 'char' values, because they + // can be upgraded to ints automatically by Java, but having them + // as ints prevented split(blah, TAB) from working + static final char BACKSPACE = 8; + static final char TAB = 9; + static final char ENTER = 10; + static final char RETURN = 13; + static final char ESC = 27; + static final char DELETE = 127; + + // i.e. if ((key == CODED) && (keyCode == UP)) + static final int CODED = 0xffff; + + // key will be CODED and keyCode will be this value + static final int UP = KeyEvent.VK_UP; + static final int DOWN = KeyEvent.VK_DOWN; + static final int LEFT = KeyEvent.VK_LEFT; + static final int RIGHT = KeyEvent.VK_RIGHT; + + // key will be CODED and keyCode will be this value + static final int ALT = KeyEvent.VK_ALT; + static final int CONTROL = KeyEvent.VK_CONTROL; + static final int SHIFT = KeyEvent.VK_SHIFT; + + + // cursor types + + static final int ARROW = Cursor.DEFAULT_CURSOR; + static final int CROSS = Cursor.CROSSHAIR_CURSOR; + static final int HAND = Cursor.HAND_CURSOR; + static final int MOVE = Cursor.MOVE_CURSOR; + static final int TEXT = Cursor.TEXT_CURSOR; + static final int WAIT = Cursor.WAIT_CURSOR; + + + // hints + + //static final int SCALE_STROKE_WIDTH = 0; + //static final int LIGHTING_AFFECTS_STROKE = 1; + static final int ENABLE_NATIVE_FONTS = 2; + static final int DISABLE_TEXT_SMOOTH = 3; + //static final int DISABLE_SMOOTH_HACK = 4; + static final int DISABLE_DEPTH_TEST = 5; + static final int NO_FLYING_POO = 6; + static final int ENABLE_DEPTH_SORT = 7; + static final int DISABLE_ERROR_REPORT = 8; + static final int ENABLE_ACCURATE_TEXTURES = 9; + + static final int HINT_COUNT = 10; +} diff --git a/core/PFont.java b/core/PFont.java new file mode 100644 index 000000000..01cefda4f --- /dev/null +++ b/core/PFont.java @@ -0,0 +1,1019 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry & Casey Reas + Portions Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.*; +import java.io.*; +import java.lang.reflect.*; + + +/** + * Grayscale bitmap font class used by Processing. + *

+ * Awful (and by that, I mean awesome) ascii (non)art for how this works: + *

+ *   |
+ *   |                   height is the full used height of the image
+ *   |
+ *   |   ..XX..       }
+ *   |   ..XX..       }
+ *   |   ......       }
+ *   |   XXXX..       }  topExtent (top y is baseline - topExtent)
+ *   |   ..XX..       }
+ *   |   ..XX..       }  dotted areas are where the image data
+ *   |   ..XX..       }  is actually located for the character
+ *   +---XXXXXX----   }  (it extends to the right and down
+ *   |                   for power of two texture sizes)
+ *   ^^^^ leftExtent (amount to move over before drawing the image
+ *
+ *   ^^^^^^^^^^^^^^ setWidth (width displaced by char)
+ * 
+ */ +public class PFont implements PConstants { + + public int charCount; + public PImage images[]; + + /** + * Native Java version of the font. If possible, this allows the + * PGraphics subclass to just use Java's font rendering stuff + * in situations where that's faster. + */ + public Font font; + + /** + * Name of the font as seen by Java when it was created. + * If the font is available, the native version will be used. + */ + public String name; + + /** + * Postscript name of the font that this bitmap was created from. + */ + public String psname; + + /** "natural" size of the font (most often 48) */ + public int size; + + /** true if smoothing was enabled for this font, used for native impl */ + public boolean smooth; + + /** next power of 2 over the max image size (usually 64) */ + public int mbox2; + + /** floating point width (convenience) */ + protected float fwidth; + + /** floating point width (convenience) */ + protected float fheight; + + /** texture width, same as mbox2, but reserved for future use */ + public int twidth; + + /** texture height, same as mbox2, but reserved for future use */ + public int theight; + + public int value[]; // char code + public int height[]; // height of the bitmap data + public int width[]; // width of bitmap data + public int setWidth[]; // width displaced by the char + public int topExtent[]; // offset for the top + public int leftExtent[]; // offset for the left + + public int ascent; + public int descent; + + protected int ascii[]; // quick lookup for the ascii chars + + // shared by the text() functions to avoid incessant allocation of memory + //protected char textBuffer[] = new char[8 * 1024]; + //protected char widthBuffer[] = new char[8 * 1024]; + + + public PFont() { } // for subclasses + + + public PFont(InputStream input) throws IOException { + DataInputStream is = new DataInputStream(input); + + // number of character images stored in this font + charCount = is.readInt(); + + // bit count is ignored since this is always 8 + //int numBits = is.readInt(); + // used to be the bitCount, but now used for version number. + // version 8 is any font before 69, so 9 is anything from 83+ + // 9 was buggy so gonna increment to 10. + int version = is.readInt(); + + // this was formerly ignored, now it's the actual font size + //mbox = is.readInt(); + size = is.readInt(); + // this was formerly mboxY, the one that was used + // this will make new fonts downward compatible + //mbox2 = is.readInt(); + mbox2 = is.readInt(); + + fwidth = size; //mbox; + fheight = size; //mbox; + + // size for image ("texture") is next power of 2 + // over the font size. for most vlw fonts, the size is 48 + // so the next power of 2 is 64. + // double-check to make sure that mbox2 is a power of 2 + // there was a bug in the old font generator that broke this + //mbox2 = (int) Math.pow(2, Math.ceil(Math.log(mbox2) / Math.log(2))); + mbox2 = (int) Math.pow(2, Math.ceil(Math.log(mbox2) / Math.log(2))); + // size for the texture is stored in the font + twidth = theight = mbox2; //mbox2; + + ascent = is.readInt(); // formerly baseHt (zero/ignored) + descent = is.readInt(); // formerly ignored struct padding + + // allocate enough space for the character info + value = new int[charCount]; + height = new int[charCount]; + width = new int[charCount]; + setWidth = new int[charCount]; + topExtent = new int[charCount]; + leftExtent = new int[charCount]; + + ascii = new int[128]; + for (int i = 0; i < 128; i++) ascii[i] = -1; + + // read the information about the individual characters + for (int i = 0; i < charCount; i++) { + value[i] = is.readInt(); + height[i] = is.readInt(); + width[i] = is.readInt(); + setWidth[i] = is.readInt(); + topExtent[i] = is.readInt(); + leftExtent[i] = is.readInt(); + + // pointer in the c version, ignored + is.readInt(); + + // cache locations of the ascii charset + if (value[i] < 128) ascii[value[i]] = i; + + // the values for getAscent() and getDescent() from FontMetrics + // seem to be way too large.. perhaps they're the max? + // as such, use a more traditional marker for ascent/descent + if (value[i] == 'd') { + if (ascent == 0) ascent = topExtent[i]; + } + if (value[i] == 'p') { + if (descent == 0) descent = -topExtent[i] + height[i]; + } + } + + // not a roman font, so throw an error and ask to re-build. + // that way can avoid a bunch of error checking hacks in here. + if ((ascent == 0) && (descent == 0)) { + throw new RuntimeException("Please use \"Create Font\" to " + + "re-create this font."); + } + + images = new PImage[charCount]; + for (int i = 0; i < charCount; i++) { + images[i] = new PImage(twidth, theight, ALPHA); + int bitmapSize = height[i] * width[i]; + + byte temp[] = new byte[bitmapSize]; + is.readFully(temp); + + // convert the bitmap to an alpha channel + int w = width[i]; + int h = height[i]; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int valu = temp[y*w + x] & 0xff; + images[i].pixels[y * twidth + x] = valu; + //(valu << 24) | 0xFFFFFF; // windows + //0xFFFFFF00 | valu; // macosx + + //System.out.print((images[i].pixels[y*64+x] > 128) ? "*" : "."); + } + //System.out.println(); + } + //System.out.println(); + } + + if (version >= 10) { // includes the font name at the end of the file + name = is.readUTF(); + psname = is.readUTF(); + } + if (version == 11) { + smooth = is.readBoolean(); + } + } + + + /** + * Try to find the native version of this font. + */ + protected Font findFont() { + // this font may or may not be installed + font = new Font(name, Font.PLAIN, size); + // if the ps name matches, then we're in fine shape + if (!font.getPSName().equals(psname)) { + // on osx java 1.4 (not 1.3.. ugh), you can specify the ps name + // of the font, so try that in case this .vlw font was created on pc + // and the name is different, but the ps name is found on the + // java 1.4 mac that's currently running this sketch. + font = new Font(psname, Font.PLAIN, size); + } + // check again, and if still bad, screw em + if (!font.getPSName().equals(psname)) { + font = null; + } + return font; + } + + + /** + * Write this PFont to an OutputStream. + *

+ * This is used by the Create Font tool, or whatever anyone else dreams + * up for messing with fonts themselves. + *

+ * It is assumed that the calling class will handle closing + * the stream when finished. + */ + public void save(OutputStream output) throws IOException { + DataOutputStream os = new DataOutputStream(output); + + os.writeInt(charCount); + + if ((name == null) || (psname == null)) { + name = ""; + psname = ""; + } + // formerly numBits, now used for version number + //os.writeInt((name != null) ? 11 : 8); + os.writeInt(11); + + os.writeInt(size); // formerly mboxX (was 64, now 48) + os.writeInt(mbox2); // formerly mboxY (was 64, still 64) + os.writeInt(ascent); // formerly baseHt (was ignored) + os.writeInt(descent); // formerly struct padding for c version + + for (int i = 0; i < charCount; i++) { + os.writeInt(value[i]); + os.writeInt(height[i]); + os.writeInt(width[i]); + os.writeInt(setWidth[i]); + os.writeInt(topExtent[i]); + os.writeInt(leftExtent[i]); + os.writeInt(0); // padding + } + + for (int i = 0; i < charCount; i++) { + for (int y = 0; y < height[i]; y++) { + for (int x = 0; x < width[i]; x++) { + os.write(images[i].pixels[y * mbox2 + x] & 0xff); + } + } + } + + //if (name != null) { // version 11 + os.writeUTF(name); + os.writeUTF(psname); + os.writeBoolean(smooth); + //} + + os.flush(); + } + + + /** + * Get index for the char (convert from unicode to bagel charset). + * @return index into arrays or -1 if not found + */ + public int index(char c) { + // degenerate case, but the find function will have trouble + // if there are somehow zero chars in the lookup + //if (value.length == 0) return -1; + if (charCount == 0) return -1; + + // quicker lookup for the ascii fellers + if (c < 128) return ascii[c]; + + // some other unicode char, hunt it out + //return index_hunt(c, 0, value.length-1); + return indexHunt(c, 0, charCount-1); + } + + + protected int indexHunt(int c, int start, int stop) { + int pivot = (start + stop) / 2; + + // if this is the char, then return it + if (c == value[pivot]) return pivot; + + // char doesn't exist, otherwise would have been the pivot + //if (start == stop) return -1; + if (start >= stop) return -1; + + // if it's in the lower half, continue searching that + if (c < value[pivot]) return indexHunt(c, start, pivot-1); + + // if it's in the upper half, continue there + return indexHunt(c, pivot+1, stop); + } + + + /** + * Currently un-implemented for .vlw fonts, + * but honored for layout in case subclasses use it. + */ + public float kern(char a, char b) { + return 0; + } + + + /** + * Returns the ascent of this font from the baseline. + * The value is based on a font of size 1. + */ + public float ascent() { + return ((float)ascent / fheight); + } + + + /** + * Returns how far this font descends from the baseline. + * The value is based on a font size of 1. + */ + public float descent() { + return ((float)descent / fheight); + } + + + /** + * Width of this character for a font of size 1. + */ + public float width(char c) { + if (c == 32) return width('i'); + + int cc = index(c); + if (cc == -1) return 0; + + return ((float)setWidth[cc] / fwidth); + } + + + /** + * Draw a character at an x, y position. + */ + /* + public void text(char c, float x, float y, PGraphics parent) { + text(c, x, y, 0, parent); + } + */ + + /** + * Draw a character at an x, y, z position. + */ + /* + public void text(char c, float x, float y, float z, PGraphics parent) { + if (parent.textAlign == CENTER) { + x -= parent.textSize * width(c) / 2f; + + } else if (parent.textAlign == RIGHT) { + x -= parent.textSize * width(c); + } + + //textImpl(c, x, y, z, parent); + if (z != 0) parent.translate(0, 0, z); // TEMPORARY HACK! SLOW! + parent.textImpl(c, x, y, z); + if (z != 0) parent.translate(0, 0, -z); // TEMPORARY HACK! SLOW! + } + */ + + + /* + public void text(String str, float x, float y, PGraphics parent) { + text(str, x, y, 0, parent); + } + + + public void text(String str, float x, float y, float z, PGraphics parent) { + if (z != 0) parent.translate(0, 0, z); // TEMPORARY HACK! SLOW! + + int length = str.length(); + if (length > textBuffer.length) { + textBuffer = new char[length + 10]; + } + str.getChars(0, length, textBuffer, 0); + + int start = 0; + int index = 0; + while (index < length) { + if (textBuffer[index] == '\n') { + textLine(start, index, x, y, z, parent); + start = index + 1; + y += parent.textLeading; + } + index++; + } + if (start < length) { + textLine(start, index, x, y, z, parent); + } + if (z != 0) parent.translate(0, 0, -z); // TEMPORARY HACK! SLOW! + } + + + protected void textLine(int start, int stop, + float x, float y, float z, + PGraphics parent) { + if (parent.textAlign == CENTER) { + x -= parent.textSize * calcWidth(textBuffer, start, stop) / 2f; + + } else if (parent.textAlign == RIGHT) { + x -= parent.textSize * calcWidth(textBuffer, start, stop); + } + + for (int index = start; index < stop; index++) { + //textImpl(textBuffer[index], x, y, z, parent); + //parent.textImpl(textBuffer[index], x, y, z); + // HACK FOR Z COORDINATES.. FIX ME SOON + parent.textImpl(textBuffer[index], x, y, 0); //z); + x += parent.textSize *width(textBuffer[index]); + } + } + */ + + + /** + * Same as below, just without a z coordinate. + */ + /* + public void text(String str, float x, float y, + float c, float d, PGraphics parent) { + text(str, x, y, c, d, 0, parent); + } + */ + + /** + * Draw text in a box that is constrained to a particular size. + *

+ * The parent PApplet will have converted the coords based on + * the current rectMode(). + *

+ * Note that the x,y coords of the start of the box + * will align with the *ascent* of the text, not the baseline, + * as is the case for the other text() functions. + */ + /* + public void text(String str, float boxX1, float boxY1, + float boxX2, float boxY2, float boxZ, PGraphics parent) { + if (boxZ != 0) parent.translate(0, 0, boxZ); // TEMPORARY HACK! SLOW! + + float spaceWidth = width(' ') * parent.textSize; + float runningX = boxX1; + float currentY = boxY1; + float boxWidth = boxX2 - boxX1; + + float lineX = boxX1; + if (parent.textAlign == CENTER) { + lineX = lineX + boxWidth/2f; + } else if (parent.textAlign == RIGHT) { + lineX = boxX2; + } + + // ala illustrator, the text itself must fit inside the box + currentY += ascent() * parent.textSize; + // if the box is already too small, tell em to f off + if (currentY > boxY2) return; + + int length = str.length(); + if (length > textBuffer.length) { + textBuffer = new char[length + 10]; + } + str.getChars(0, length, textBuffer, 0); + + int wordStart = 0; + int wordStop = 0; + int lineStart = 0; + int index = 0; + while (index < length) { + if ((textBuffer[index] == ' ') || (index == length-1)) { + // boundary of a word + float wordWidth = parent.textSize * + calcWidth(textBuffer, wordStart, index); + + if (runningX + wordWidth > boxX2) { + if (runningX == boxX1) { + // if this is the first word, and its width is + // greater than the width of the text box, + // then break the word where at the max width, + // and send the rest of the word to the next line. + do { + index--; + if (index == wordStart) { + // not a single char will fit on this line. screw 'em. + //System.out.println("screw you"); + return; + } + wordWidth = parent.textSize * + calcWidth(textBuffer, wordStart, index); + } while (wordWidth > boxWidth); + textLine(lineStart, index, lineX, currentY, boxZ, parent); + + } else { + // next word is too big, output current line + // and advance to the next line + textLine(lineStart, wordStop, lineX, currentY, boxZ, parent); + // only increment index if a word wasn't broken inside the + // do/while loop above.. also, this is a while() loop too, + // because multiple spaces don't count for shit when they're + // at the end of a line like this. + + index = wordStop; // back that ass up + while ((index < length) && + (textBuffer[index] == ' ')) { + index++; + } + } + lineStart = index; + wordStart = index; + wordStop = index; + runningX = boxX1; + currentY += parent.textLeading; + if (currentY > boxY2) return; // box is now full + + } else { + runningX += wordWidth + spaceWidth; + // on to the next word + wordStop = index; + wordStart = index + 1; + } + + } else if (textBuffer[index] == '\n') { + if (lineStart != index) { // if line is not empty + textLine(lineStart, index, lineX, currentY, boxZ, parent); + } + lineStart = index + 1; + wordStart = lineStart; + currentY += parent.textLeading; + if (currentY > boxY2) return; // box is now full + } + index++; + } + if ((lineStart < length) && (lineStart != index)) { + textLine(lineStart, index, lineX, currentY, boxZ, parent); + } + + if (boxZ != 0) parent.translate(0, 0, -boxZ); // TEMPORARY HACK! SLOW! + } + */ + + + ////////////////////////////////////////////////////////////// + + + static final char[] EXTRA_CHARS = { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00BA, + 0x00BB, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, + 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, + 0x00CE, 0x00CF, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, + 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, + 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FF, 0x0102, 0x0103, + 0x0104, 0x0105, 0x0106, 0x0107, 0x010C, 0x010D, 0x010E, 0x010F, + 0x0110, 0x0111, 0x0118, 0x0119, 0x011A, 0x011B, 0x0131, 0x0139, + 0x013A, 0x013D, 0x013E, 0x0141, 0x0142, 0x0143, 0x0144, 0x0147, + 0x0148, 0x0150, 0x0151, 0x0152, 0x0153, 0x0154, 0x0155, 0x0158, + 0x0159, 0x015A, 0x015B, 0x015E, 0x015F, 0x0160, 0x0161, 0x0162, + 0x0163, 0x0164, 0x0165, 0x016E, 0x016F, 0x0170, 0x0171, 0x0178, + 0x0179, 0x017A, 0x017B, 0x017C, 0x017D, 0x017E, 0x0192, 0x02C6, + 0x02C7, 0x02D8, 0x02D9, 0x02DA, 0x02DB, 0x02DC, 0x02DD, 0x03A9, + 0x03C0, 0x2013, 0x2014, 0x2018, 0x2019, 0x201A, 0x201C, 0x201D, + 0x201E, 0x2020, 0x2021, 0x2022, 0x2026, 0x2030, 0x2039, 0x203A, + 0x2044, 0x20AC, 0x2122, 0x2202, 0x2206, 0x220F, 0x2211, 0x221A, + 0x221E, 0x222B, 0x2248, 0x2260, 0x2264, 0x2265, 0x25CA, 0xF8FF, + 0xFB01, 0xFB02 + }; + + + /** + * The default Processing character set. + *

+ * This is the union of the Mac Roman and Windows ANSI (CP1250) + * character sets. ISO 8859-1 Latin 1 is Unicode characters 0x80 -> 0xFF, + * and would seem a good standard, but in practice, most P5 users would + * rather have characters that they expect from their platform's fonts. + *

+ * This is more of an interim solution until a much better + * font solution can be determined. (i.e. create fonts on + * the fly from some sort of vector format). + *

+ * Not that I expect that to happen. + */ + static public char[] DEFAULT_CHARSET; + static { + DEFAULT_CHARSET = new char[126-33+1 + EXTRA_CHARS.length]; + int index = 0; + for (int i = 33; i <= 126; i++) { + DEFAULT_CHARSET[index++] = (char)i; + } + for (int i = 0; i < EXTRA_CHARS.length; i++) { + DEFAULT_CHARSET[index++] = EXTRA_CHARS[i]; + } + }; + + + /** + * Use reflection to create a new .vlw font on the fly. + * This only works with Java 1.3 and higher. + * + * @param font the font object to create from + * @param charset array of all unicode chars that should be included + * @param smooth true to enable smoothing/anti-aliasing + */ + public PFont(Font font, boolean smooth, char charset[]) { + if (PApplet.javaVersion < 1.3f) { + throw new RuntimeException("Can only create fonts with " + + "Java 1.3 or higher"); + } + + // save this so that we can use the native version + this.font = font; + this.smooth = smooth; + + name = font.getName(); + psname = font.getPSName(); + + try { + // fix regression from sorting (bug #564) + if (charset != null) { + // charset needs to be sorted to make index lookup run more quickly + // http://dev.processing.org/bugs/show_bug.cgi?id=494 + //Arrays.sort(charset); + Class arraysClass = Class.forName("java.util.Arrays"); + Method sortMethod = + arraysClass.getMethod("sort", new Class[] { charset.getClass() }); + sortMethod.invoke(null, new Object[] { charset }); + } + + // the count gets reset later based on how many of + // the chars are actually found inside the font. + this.charCount = (charset == null) ? 65536 : charset.length; + this.size = font.getSize(); + + fwidth = fheight = size; + + PImage bitmaps[] = new PImage[charCount]; + + // allocate enough space for the character info + value = new int[charCount]; + height = new int[charCount]; + width = new int[charCount]; + setWidth = new int[charCount]; + topExtent = new int[charCount]; + leftExtent = new int[charCount]; + + ascii = new int[128]; + for (int i = 0; i < 128; i++) ascii[i] = -1; + + int mbox3 = size * 3; + + /* + BufferedImage playground = + new BufferedImage(mbox3, mbox3, BufferedImage.TYPE_INT_RGB); + + Graphics2D g = (Graphics2D) playground.getGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + smooth ? + RenderingHints.VALUE_ANTIALIAS_ON : + RenderingHints.VALUE_ANTIALIAS_OFF); + */ + + Class bufferedImageClass = + Class.forName("java.awt.image.BufferedImage"); + Constructor bufferedImageConstructor = + bufferedImageClass.getConstructor(new Class[] { + Integer.TYPE, + Integer.TYPE, + Integer.TYPE }); + Field typeIntRgbField = bufferedImageClass.getField("TYPE_INT_RGB"); + int typeIntRgb = typeIntRgbField.getInt(typeIntRgbField); + Object playground = + bufferedImageConstructor.newInstance(new Object[] { + new Integer(mbox3), + new Integer(mbox3), + new Integer(typeIntRgb) }); + + Class graphicsClass = + Class.forName("java.awt.Graphics2D"); + Method getGraphicsMethod = + bufferedImageClass.getMethod("getGraphics", new Class[] { }); + //Object g = getGraphicsMethod.invoke(playground, new Object[] { }); + Graphics g = (Graphics) + getGraphicsMethod.invoke(playground, new Object[] { }); + + Class renderingHintsClass = + Class.forName("java.awt.RenderingHints"); + Class renderingHintsKeyClass = + Class.forName("java.awt.RenderingHints$Key"); + //PApplet.printarr(renderingHintsClass.getFields()); + + Field antialiasingKeyField = + renderingHintsClass.getField("KEY_TEXT_ANTIALIASING"); + Object antialiasingKey = + antialiasingKeyField.get(renderingHintsClass); + + Field antialiasField = smooth ? + renderingHintsClass.getField("VALUE_TEXT_ANTIALIAS_ON") : + renderingHintsClass.getField("VALUE_TEXT_ANTIALIAS_OFF"); + Object antialiasState = + antialiasField.get(renderingHintsClass); + + Method setRenderingHintMethod = + graphicsClass.getMethod("setRenderingHint", + new Class[] { renderingHintsKeyClass, + Object.class }); + setRenderingHintMethod.invoke(g, new Object[] { + antialiasingKey, + antialiasState + }); + + g.setFont(font); + FontMetrics metrics = g.getFontMetrics(); + + Method canDisplayMethod = null; + Method getDataMethod = null; + Method getSamplesMethod = null; + + int samples[] = new int[mbox3 * mbox3]; + + canDisplayMethod = + Font.class.getMethod("canDisplay", new Class[] { Character.TYPE }); + getDataMethod = + bufferedImageClass.getMethod("getData", new Class[] { }); + Class rasterClass = Class.forName("java.awt.image.Raster"); + getSamplesMethod = rasterClass.getMethod("getSamples", new Class[] { + Integer.TYPE, + Integer.TYPE, + Integer.TYPE, + Integer.TYPE, + Integer.TYPE, + // integer array type? + //Array.class + samples.getClass() + }); + + //} catch (Exception e) { + //e.printStackTrace(); + //return; + //} + + //Array samples = Array.newInstance(Integer.TYPE, mbox3*mbox3); + + int maxWidthHeight = 0; + int index = 0; + for (int i = 0; i < charCount; i++) { + char c = (charset == null) ? (char)i : charset[i]; + + //if (!font.canDisplay(c)) { // skip chars not in the font + try { + Character ch = new Character(c); + Boolean canDisplay = (Boolean) + canDisplayMethod.invoke(font, new Object[] { ch }); + if (canDisplay.booleanValue() == false) { + continue; + } + } catch (Exception e) { + e.printStackTrace(); + return; + } + + g.setColor(Color.white); + g.fillRect(0, 0, mbox3, mbox3); + g.setColor(Color.black); + g.drawString(String.valueOf(c), size, size * 2); + + // grabs copy of the current data.. so no updates (do each time) + /* + Raster raster = playground.getData(); + raster.getSamples(0, 0, mbox3, mbox3, 0, samples); + */ + + Object raster = getDataMethod.invoke(playground, new Object[] {}); + getSamplesMethod.invoke(raster, new Object[] { + new Integer(0), + new Integer(0), + new Integer(mbox3), + new Integer(mbox3), + new Integer(0), + samples + }); + + //int w = metrics.charWidth(c); + int minX = 1000, maxX = 0; + int minY = 1000, maxY = 0; + boolean pixelFound = false; + + for (int y = 0; y < mbox3; y++) { + for (int x = 0; x < mbox3; x++) { + //int sample = raster.getSample(x, y, 0); // maybe? + int sample = samples[y * mbox3 + x] & 0xff; + // or int samples[] = raster.getPixel(x, y, null); + + //if (sample == 0) { // or just not white? hmm + if (sample != 255) { + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + pixelFound = true; + //System.out.println(x + " " + y + " = " + sample); + } + } + } + + if (!pixelFound) { + //System.out.println("no pixels found in unicode char " + c + + // "(" + PApplet.hex(c) + ")"); + // this was dumb that it was set to 20 & 30, because for small + // fonts, those guys don't exist + minX = minY = 0; //20; + maxX = maxY = 0; //30; + + // this will create a 1 pixel white (clear) character.. + // maybe better to set one to -1 so nothing is added? + /* + } else { + System.out.println(PApplet.hex(c) + " has bounds " + + minX + ", " + minY + " to " + + maxX + ", " + maxY); + */ + } + + value[index] = c; + height[index] = (maxY - minY) + 1; + width[index] = (maxX - minX) + 1; + setWidth[index] = metrics.charWidth(c); + //System.out.println((char)c + " " + setWidth[index]); + + // cache locations of the ascii charset + //if (value[i] < 128) ascii[value[i]] = i; + if (c < 128) ascii[c] = index; + + // offset from vertical location of baseline + // of where the char was drawn (size*2) + topExtent[index] = size*2 - minY; + + // offset from left of where coord was drawn + leftExtent[index] = minX - size; + + if (c == 'd') { + ascent = topExtent[index]; + } + if (c == 'p') { + descent = -topExtent[index] + height[index]; + } + + if (width[index] > maxWidthHeight) maxWidthHeight = width[index]; + if (height[index] > maxWidthHeight) maxWidthHeight = height[index]; + + bitmaps[index] = new PImage(width[index], height[index], ALPHA); + + for (int y = minY; y <= maxY; y++) { + for (int x = minX; x <= maxX; x++) { + int val = 255 - (samples[y * mbox3 + x] & 0xff); + int pindex = (y - minY) * width[index] + (x - minX); + bitmaps[index].pixels[pindex] = val; + } + } + index++; + } + charCount = index; + + // foreign font, so just make ascent the max topExtent + if ((ascent == 0) && (descent == 0)) { + for (int i = 0; i < charCount; i++) { + char cc = (char) value[i]; + if (Character.isWhitespace(cc) || + (cc == '\u00A0') || (cc == '\u2007') || (cc == '\u202F')) { + continue; + } + if (topExtent[i] > ascent) { + ascent = topExtent[i]; + } + int d = -topExtent[i] + height[i]; + if (d > descent) { + descent = d; + } + } + } + // size for image/texture is next power of 2 over largest char + mbox2 = (int) + Math.pow(2, Math.ceil(Math.log(maxWidthHeight) / Math.log(2))); + twidth = theight = mbox2; + + // shove the smaller PImage data into textures of next-power-of-2 size, + // so that this font can be used immediately by p5. + images = new PImage[charCount]; + for (int i = 0; i < charCount; i++) { + images[i] = new PImage(mbox2, mbox2, ALPHA); + for (int y = 0; y < height[i]; y++) { + System.arraycopy(bitmaps[i].pixels, y*width[i], + images[i].pixels, y*mbox2, + width[i]); + } + bitmaps[i] = null; + } + + } catch (Exception e) { // catch-all for reflection stuff + e.printStackTrace(); + throw new RuntimeException(e.getMessage()); + } + } + + + /** + * Get a list of the fonts installed on the system that can be used + * by Java. Not all fonts can be used in Java, in fact it's mostly + * only TrueType fonts. OpenType fonts with CFF data such as Adobe's + * OpenType fonts seem to have trouble (even though they're sort of + * TrueType fonts as well, or may have a .ttf extension). Regular + * PostScript fonts seem to work O.K. though. + *

+ * Not recommended for use in applets, but this is implemented + * in PFont because the Java methods to access this information + * have changed between 1.1 and 1.4, and the 1.4 method is + * typical of the sort of undergraduate-level over-abstraction + * that the seems to have made its way into the Java API after 1.1. + */ + static public String[] list() { + if (PApplet.javaVersion < 1.3f) { + // make this reflection too, since compilers complain about the + // deprecation, and it's bound to stop working in 1.6 or something + //return Toolkit.getDefaultToolkit().getFontList(); + try { + Toolkit tk = Toolkit.getDefaultToolkit(); + Method getFontListMethod = + tk.getClass().getMethod("getFontList", (Class[]) null); + return (String[]) getFontListMethod.invoke(tk, (Object[]) null); + } catch (Exception e) { + e.printStackTrace(); + return new String[] { }; + } + } + + // getFontList is deprecated in 1.4, so this has to be used + try { + //GraphicsEnvironment ge = + // GraphicsEnvironment.getLocalGraphicsEnvironment(); + Class geClass = Class.forName("java.awt.GraphicsEnvironment"); + Method glgeMethod = + geClass.getMethod("getLocalGraphicsEnvironment", (Class[]) null); + Object ge = glgeMethod.invoke((Class[]) null, (Object[]) null); + + //Font fonts[] = ge.getAllFonts(); + Method gafMethod = geClass.getMethod("getAllFonts", (Class[]) null); + Font fonts[] = (Font[]) gafMethod.invoke(ge, (Object[]) null); + String list[] = new String[fonts.length]; + for (int i = 0; i < list.length; i++) { + list[i] = fonts[i].getName(); + } + return list; + + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error inside PFont.list()"); + } + } +} diff --git a/core/PGraphics.java b/core/PGraphics.java new file mode 100644 index 000000000..c1318177d --- /dev/null +++ b/core/PGraphics.java @@ -0,0 +1,4308 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.*; +import java.awt.image.*; + + +/** + * Main graphics and rendering context, as well as + * the base API implementation for processing "core". + *

+ * As of beta, this class is semi-disabled. + */ +public abstract class PGraphics extends PImage implements PConstants { + + // transformed values + // (to be used in rendering) + + static public final int X = 0; // transformed xyzw + static public final int Y = 1; // formerly SX SY SZ + static public final int Z = 2; + + static public final int R = 3; // actual rgb, after lighting + static public final int G = 4; // fill stored here, transform in place + static public final int B = 5; + static public final int A = 6; + + // values that need no transformation + // but will be used in rendering + + static public final int U = 7; // texture + static public final int V = 8; + + // incoming values, raw and untransformed + // (won't be used in rendering) + + static public final int MX = 9; // model coords xyz + static public final int MY = 10; + static public final int MZ = 11; + + /** stroke argb values */ + static public final int SR = 12; + static public final int SG = 13; + static public final int SB = 14; + static public final int SA = 15; + + /** stroke weight */ + static public final int SW = 16; + + // not used in rendering + // only used for calculating colors + + static public final int NX = 17; // normal + static public final int NY = 18; + static public final int NZ = 19; + + static public final int VX = 20; // view space coords + static public final int VY = 21; + static public final int VZ = 22; + static public final int VW = 23; + + // Ambient color (usually to be kept the same as diffuse) + // fill(_) sets both ambient and diffuse. + static public final int AR = 24; + static public final int AG = 25; + static public final int AB = 26; + + // Diffuse is shared with fill. + static public final int DR = 3; + static public final int DG = 4; + static public final int DB = 5; + static public final int DA = 6; + + //specular (by default kept white) + static public final int SPR = 27; + static public final int SPG = 28; + static public final int SPB = 29; + //GL doesn't use a separate specular alpha, but we do (we're better) + static public final int SPA = 30; + + static public final int SHINE = 31; + + //emissive (by default kept black) + static public final int ER = 32; + static public final int EG = 33; + static public final int EB = 34; + + //has this vertex been lit yet + static public final int BEEN_LIT = 35; + + static final int VERTEX_FIELD_COUNT = 36; + + // line & triangle fields (note how these overlap) + + static public final int INDEX = 0; // shape index + static public final int VERTEX1 = 1; + static public final int VERTEX2 = 2; + static public final int VERTEX3 = 3; // (triangles only) + static public final int TEXTURE_INDEX = 4; // (triangles only) + static public final int STROKE_MODE = 3; // (lines only) + static public final int STROKE_WEIGHT = 4; // (lines only) + + static public final int LINE_FIELD_COUNT = 5; + static public final int TRIANGLE_FIELD_COUNT = 5; + + static public final int TRI_DIFFUSE_R = 0; + static public final int TRI_DIFFUSE_G = 1; + static public final int TRI_DIFFUSE_B = 2; + static public final int TRI_DIFFUSE_A = 3; + static public final int TRI_SPECULAR_R = 4; + static public final int TRI_SPECULAR_G = 5; + static public final int TRI_SPECULAR_B = 6; + static public final int TRI_SPECULAR_A = 7; + + static public final int TRIANGLE_COLOR_COUNT = 8; + + + // normal modes for lighting, these have the uglier naming + // because the constants are never seen by users + + /// normal calculated per triangle + static public final int AUTO_NORMAL = 0; + /// one normal manually specified per shape + static public final int MANUAL_SHAPE_NORMAL = 1; + /// normals specified for each shape vertex + static public final int MANUAL_VERTEX_NORMAL = 2; + + + /// width minus one (useful for many calculations) + public int width1; + + /// height minus one (useful for many calculations) + public int height1; + + /// width * height (useful for many calculations) + public int pixelCount; + + /// true if defaults() has been called a first time + protected boolean defaultsInited; + + /// true if in-between beginDraw() and endDraw() + protected boolean insideDraw; + + /// true if in the midst of resize (no drawing can take place) + boolean insideResize; + + // ........................................................ + + /** + * true if this is the main drawing surface for a particular sketch. + * This would be set to false for an offscreen buffer or if it were + * created any other way than size(). When this is set, the listeners + * are also added to the sketch. + */ + protected boolean mainDrawingSurface; + + // ........................................................ + + // specifics for java memoryimagesource + DirectColorModel cm; + MemoryImageSource mis; + public Image image; + + // ........................................................ + + // used by recordRaw() + public PGraphics raw; + + // ........................................................ + + // needs to happen before background() is called + // and resize.. so it's gotta be outside + protected boolean hints[] = new boolean[HINT_COUNT]; + + // ........................................................ + + // underscored_names are used for private functions or variables + + /** The current colorMode */ + public int colorMode; // = RGB; + + /** Max value for red (or hue) set by colorMode */ + public float colorModeX; // = 255; + + /** Max value for green (or saturation) set by colorMode */ + public float colorModeY; // = 255; + + /** Max value for blue (or value) set by colorMode */ + public float colorModeZ; // = 255; + + /** Max value for alpha set by colorMode */ + public float colorModeA; // = 255; + + /** True if colors are not in the range 0..1 */ + boolean colorScale; // = true; + + /** True if colorMode(RGB, 255) */ + boolean colorRgb255; // = true; + + // ........................................................ + + /** + * true if tint() is enabled (read-only). + * Using tint/tintColor seems a better option for naming than + * tintEnabled/tint because the latter seems ugly, even though + * g.tint as the actual color seems a little more intuitive, + * it's just that g.tintEnabled is even more unintuitive. + * Same goes for fill and stroke et al. + */ + public boolean tint; + + /** tint that was last set (read-only) */ + public int tintColor; + + protected boolean tintAlpha; + protected float tintR, tintG, tintB, tintA; + protected int tintRi, tintGi, tintBi, tintAi; + + // ........................................................ + + /** true if fill() is enabled, (read-only) */ + public boolean fill; + + /** fill that was last set (read-only) */ + public int fillColor = 0xffFFFFFF; + + protected boolean fillAlpha; + protected float fillR, fillG, fillB, fillA; + protected int fillRi, fillGi, fillBi, fillAi; + + // ........................................................ + + /** true if stroke() is enabled, (read-only) */ + public boolean stroke; + + /** stroke that was last set (read-only) */ + public int strokeColor = 0xff000000; + + protected boolean strokeAlpha; + protected float strokeR, strokeG, strokeB, strokeA; + protected int strokeRi, strokeGi, strokeBi, strokeAi; + + // ........................................................ + + /** Last background color that was set, zero if an image */ + public int backgroundColor = 0xffCCCCCC; + + protected boolean backgroundAlpha; + protected float backgroundR, backgroundG, backgroundB, backgroundA; + protected int backgroundRi, backgroundGi, backgroundBi, backgroundAi; + + // ........................................................ + + // internal color for setting/calculating + protected float calcR, calcG, calcB, calcA; + int calcRi, calcGi, calcBi, calcAi; + int calcColor; + boolean calcAlpha; + + /** The last rgb value converted to HSB */ + int cacheHsbKey; + /** Result of the last conversion to HSB */ + float cacheHsbValue[] = new float[3]; // inits to zero + + // ........................................................ + + /** + * Last value set by strokeWeight() (read-only). This has a default + * setting, rather than fighting with renderers about whether that + * renderer supports thick lines. + */ + public float strokeWeight = 1; + + /** + * Set by strokeJoin() (read-only). This has a default setting + * so that strokeJoin() need not be called by defaults, + * because subclasses may not implement it (i.e. PGraphicsGL) + */ + public int strokeJoin = MITER; + + /** + * Set by strokeCap() (read-only). This has a default setting + * so that strokeCap() need not be called by defaults, + * because subclasses may not implement it (i.e. PGraphicsGL) + */ + public int strokeCap = ROUND; + + // ........................................................ + + /** + * Model transformation of the form m[row][column], + * which is a "column vector" (as opposed to "row vector") matrix. + */ + public float m00, m01, m02, m03; + public float m10, m11, m12, m13; + public float m20, m21, m22, m23; + public float m30, m31, m32, m33; + + static final int MATRIX_STACK_DEPTH = 32; + float matrixStack[][] = new float[MATRIX_STACK_DEPTH][16]; + int matrixStackDepth; + + // ........................................................ + + //Path path; + + // ........................................................ + + /** + * Type of shape passed to beginShape(), + * zero if no shape is currently being drawn. + */ + protected int shape; + + // vertices + static final int DEFAULT_VERTICES = 512; + protected float vertices[][] = + new float[DEFAULT_VERTICES][VERTEX_FIELD_COUNT]; + protected int vertexCount; // total number of vertices + + + // ........................................................ + + protected boolean bezierInited = false; + public int bezierDetail = 20; + // msjvm complained when bezier_basis was final + protected float bezier_basis[][] = { + { -1, 3, -3, 1}, + { 3, -6, 3, 0}, + { -3, 3, 0, 0}, + { 1, 0, 0, 0} + }; + + protected PMatrix bezierBasis = + new PMatrix(-1, 3, -3, 1, + 3, -6, 3, 0, + -3, 3, 0, 0, + 1, 0, 0, 0); + + protected float bezierForwardMatrix[][]; // = new float[4][4]; + protected float bezierDrawMatrix[][]; // = new float[4][4]; + + // ........................................................ + + protected boolean curve_inited = false; + protected int curveDetail = 20; + // catmull-rom basis matrix, perhaps with optional s parameter + public float curveTightness = 0; + protected float curve_basis[][]; // = new float[4][4]; + protected float curve_forward[][]; // = new float[4][4]; + protected float curve_draw[][]; + + protected PMatrix bezierBasisInverse; + protected PMatrix curveToBezierMatrix; + + // ........................................................ + + // spline vertices + + static final int DEFAULT_SPLINE_VERTICES = 128; + protected float splineVertices[][]; + protected int splineVertexCount; + + // ........................................................ + + // precalculate sin/cos lookup tables [toxi] + // circle resolution is determined from the actual used radii + // passed to ellipse() method. this will automatically take any + // scale transformations into account too + + // [toxi 031031] + // changed table's precision to 0.5 degree steps + // introduced new vars for more flexible code + static final protected float sinLUT[]; + static final protected float cosLUT[]; + static final protected float SINCOS_PRECISION = 0.5f; + static final protected int SINCOS_LENGTH = (int) (360f / SINCOS_PRECISION); + static { + sinLUT = new float[SINCOS_LENGTH]; + cosLUT = new float[SINCOS_LENGTH]; + for (int i = 0; i < SINCOS_LENGTH; i++) { + sinLUT[i] = (float) Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION); + cosLUT[i] = (float) Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION); + } + } + + // ........................................................ + + /** The current rect mode (read-only) */ + public int rectMode; + + /** The current ellipse mode (read-only) */ + public int ellipseMode; + + /** The current text font (read-only) */ + public PFont textFont; + + /** The current font if a Java version of it is installed */ + public Font textFontNative; + + /** Metrics for the current native Java font */ + public FontMetrics textFontNativeMetrics; + + /** The current text align (read-only) */ + public int textAlign; + + /** The current vertical text alignment (read-only) */ + public int textAlignY; + + /** The current text mode (read-only) */ + public int textMode; + + /** The current text size (read-only) */ + public float textSize; + + /** The current text leading (read-only) */ + public float textLeading; + + /** Last text position, because text often mixed on lines together */ + public float textX, textY, textZ; + + /** + * Internal buffer used by the text() functions + * because the String object is slow + */ + protected char textBuffer[] = new char[8 * 1024]; + protected char textWidthBuffer[] = new char[8 * 1024]; + + + ////////////////////////////////////////////////////////////// + + // VARIABLES FOR 3D (used to prevent the need for a subclass) + + + /** The modelview matrix. */ + public PMatrix modelview; + + /** Inverse modelview matrix, used for lighting. */ + public PMatrix modelviewInv; + + /** + * The camera matrix, the modelview will be set to this on beginDraw. + */ + public PMatrix camera; + + /** Inverse camera matrix */ + public PMatrix cameraInv; + + // ........................................................ + + // Material properties + + public float ambientR, ambientG, ambientB; + public float specularR, specularG, specularB, specularA; + public float emissiveR, emissiveG, emissiveB; + public float shininess; + + // ........................................................ + + /** Camera field of view (in radians, as of rev 86) */ + public float cameraFOV; + + /** Position of the camera */ + public float cameraX, cameraY, cameraZ; + + public float cameraNear, cameraFar; + public float cameraAspect; + + // projection matrix + public PMatrix projection; // = new PMatrix(); + + // ........................................................ + + /// the stencil buffer + public int stencil[]; + + /// depth buffer + public float zbuffer[]; + + // ........................................................ + + /** Maximum lights by default is 8, which is arbitrary, + but is the minimum defined by OpenGL */ + public static final int MAX_LIGHTS = 8; + + public int lightCount = 0; + + /** Light types */ + public int lightType[]; + + /** Light positions */ + public float lightPosition[][]; + //public float lightsX[], lightsY[], lightsZ[]; + + /** Light direction (normalized vector) */ + public float lightNormal[][]; + //public float lightsNX[], lightsNY[], lightsNZ[]; + + /** Light falloff */ + public float lightFalloffConstant[]; + public float lightFalloffLinear[]; + public float lightFalloffQuadratic[]; + + /** Light spot angle */ + public float lightSpotAngle[]; + + /** Cosine of light spot angle */ + public float lightSpotAngleCos[]; + + /** Light spot concentration */ + public float lightSpotConcentration[]; + + /** Diffuse colors for lights. + * For an ambient light, this will hold the ambient color. + * Internally these are stored as numbers between 0 and 1. */ + public float lightDiffuse[][]; + + /** Specular colors for lights. + Internally these are stored as numbers between 0 and 1. */ + public float lightSpecular[][]; + + /** Current specular color for lighting */ + public float currentLightSpecular[]; + + /** Current light falloff */ + public float currentLightFalloffConstant; + public float currentLightFalloffLinear; + public float currentLightFalloffQuadratic; + + // ........................................................ + + /** + * Sets whether texture coordinates passed to + * vertex() calls will be based on coordinates that are + * based on the IMAGE or NORMALIZED. + */ + public int textureMode; + + /** + * Current horizontal coordinate for texture, will always + * be between 0 and 1, even if using textureMode(IMAGE). + */ + public float textureU; + + /** Current vertical coordinate for texture, see above. */ + public float textureV; + + /** Current image being used as a texture */ + public PImage textureImage; + + // ........................................................ + + /** + * Normals + */ + public float normalX, normalY, normalZ; + public int normalMode; + public int normalCount; + + // ........................................................ + + // [toxi031031] new & faster sphere code w/ support flexibile resolutions + // will be set by sphereDetail() or 1st call to sphere() + public int sphereDetail = 0; + + + + ////////////////////////////////////////////////////////////// + + // INTERNAL + + + /** + * Constructor for the PGraphics object. This prototype only exists + * because of annoying java compilers, and cannot be used. + */ + /* + protected PGraphics() { } + */ + + + /** + * Constructor for the PGraphics object. Use this to ensure that + * the defaults get set properly. In a subclass, use this(w, h) + * as the first line of a subclass' constructor to properly set + * the internal fields and defaults. + * + * @param iwidth viewport width + * @param iheight viewport height + * @param parent null unless this is the main drawing surface + */ + public PGraphics(int iwidth, int iheight, PApplet parent) { + this.parent = parent; + if (parent != null) setMainDrawingSurface(); +/* + // if this is being created by createGraphics(), the parent applet + // will be set later via another method. + if (parent != null) { + mainDrawingSurface = true; + // base images must be opaque (for performance and general + // headache reasons.. argh, a semi-transparent opengl surface?) + // use createGraphics() if you want a transparent surface. + format = RGB; + parent.addListeners(); + } +*/ + resize(iwidth, iheight); + } + + + /** + * Set this as the main drawing surface. Meaning that it can safely be + * set to opaque (given a default gray background) and listeners for + * the mouse and keyboard added. + *

+ * This should only be used by subclasses of PGraphics. + */ + public void setMainDrawingSurface() { // ignore + mainDrawingSurface = true; + // base images must be opaque (for performance and general + // headache reasons.. argh, a semi-transparent opengl surface?) + // use createGraphics() if you want a transparent surface. + format = RGB; + parent.addListeners(); + } + + + /** + * Called in repsonse to a resize event, handles setting the + * new width and height internally, as well as re-allocating + * the pixel buffer for the new size. + *

+ * Note that this will nuke any camera settings. + */ + public void resize(int iwidth, int iheight) { // ignore + //System.out.println("resize " + iwidth + " " + iheight); + insideDrawWait(); + insideResize = true; + + width = iwidth; + height = iheight; + width1 = width - 1; + height1 = height - 1; + + allocate(); + + insideResize = false; // ok to draw again + } + + + /** + * Parent thread has requested that visual action be taken. + * This is broken out like this because the OpenGL library + * handles updates in a very different way. + */ + public void requestDisplay(PApplet pa) { // ignore + pa.handleDisplay(); + } + + + // broken out because of subclassing + abstract protected void allocate(); + + + + ////////////////////////////////////////////////////////////// + + // FRAME + + + protected void insideResizeWait() { + /* + while (insideResize) { + //System.out.println("waiting"); + try { + Thread.sleep(5); + } catch (InterruptedException e) { } + } + */ + } + + + protected void insideDrawWait() { + /* + while (insideDraw) { + //System.out.println("waiting"); + try { + Thread.sleep(5); + } catch (InterruptedException e) { } + } + */ + } + + + /** + * Prepares the PGraphics for drawing. + *

+ * When creating your own PGraphics, you should call this before + * drawing anything. + */ + abstract public void beginDraw(); // ignore + + + /** + * This will finalize rendering so that it can be shown on-screen. + *

+ * When creating your own PGraphics, you should call this when + * you're finished drawing. + */ + abstract public void endDraw(); // ignore + + + /** + * Set engine's default values. This has to be called by PApplet, + * somewhere inside setup() or draw() because it talks to the + * graphics buffer, meaning that for subclasses like OpenGL, there + * needs to be a valid graphics context to mess with otherwise + * you'll get some good crashing action. + */ + public void defaults() { // ignore + //System.out.println("PGraphics.defaults() " + width + " " + height); + + colorMode(RGB, 255); + fill(255); + stroke(0); + // other stroke attributes are set in the initializers + // inside the class (see above, strokeWeight = 1 et al) + + // init shape stuff + shape = 0; + + // init matrices (must do before lights) + matrixStackDepth = 0; + + rectMode(CORNER); + ellipseMode(CENTER); + //arcMode(CENTER); + //angleMode(RADIANS); + + // no current font + textFont = null; + textSize = 12; + textLeading = 14; + textAlign = LEFT; + textMode = MODEL; + + // if this fella is associated with an applet, then clear its background. + // if it's been created by someone else through createGraphics, + // they have to call background() themselves, otherwise everything gets + // a gray background (when just a transparent surface or an empty pdf + // is what's desired) + if (mainDrawingSurface) { + //System.out.println("main drawing surface"); + background(backgroundColor); + } + + defaultsInited = true; + } + + + protected void flush() { + // no-op, mostly for P3D to write sorted stuff + } + + + + ////////////////////////////////////////////////////////////// + + // HINTS + + /** + * Enable a hint option. + *

+ * For the most part, hints are temporary api quirks, + * for which a proper api hasn't been properly worked out. + * for instance SMOOTH_IMAGES existed because smooth() + * wasn't yet implemented, but it will soon go away. + *

+ * They also exist for obscure features in the graphics + * engine, like enabling/disabling single pixel lines + * that ignore the zbuffer, the way they do in alphabot. + *

+ * Current hint options: + *

+ */ + public void hint(int which) { + hints[which] = true; + } + + + /** + * Disable a hint. + */ + public void noHint(int which) { + hints[which] = false; + } + + + + ////////////////////////////////////////////////////////////// + + // SHAPES + + /** + * Start a new shape of type POLYGON + */ + public void beginShape() { + beginShape(POLYGON); + } + + + /** + * Start a new shape. + *

+ * Differences between beginShape() and line() and point() methods. + *

+ * beginShape() is intended to be more flexible at the expense of being + * a little more complicated to use. it handles more complicated shapes + * that can consist of many connected lines (so you get joins) or lines + * mixed with curves. + *

+ * The line() and point() command are for the far more common cases + * (particularly for our audience) that simply need to draw a line + * or a point on the screen. + *

+ * From the code side of things, line() may or may not call beginShape() + * to do the drawing. In the beta code, they do, but in the alpha code, + * they did not. they might be implemented one way or the other depending + * on tradeoffs of runtime efficiency vs. implementation efficiency &mdash + * meaning the speed that things run at vs. the speed it takes me to write + * the code and maintain it. for beta, the latter is most important so + * that's how things are implemented. + */ + abstract public void beginShape(int kind); + /* + shape = kind; + + // reset vertex, line and triangle information + // every shape is rendered at endShape(); + vertexCount = 0; + + splineVertexCount = 0; + //spline_vertices_flat = true; + + //strokeChanged = false; + //fillChanged = false; + //normalChanged = false; + */ + + + public void normal(float nx, float ny, float nz) { + //depthError("normal"); + } + + + /** + * Set texture mode to either to use coordinates based on the IMAGE + * (more intuitive for new users) or NORMALIZED (better for advanced chaps) + */ + public void textureMode(int mode) { + this.textureMode = mode; + } + + + /** + * Set texture image for current shape. + * Needs to be called between @see beginShape and @see endShape + * + * @param image reference to a PImage object + */ + public void texture(PImage image) { + textureImage = image; + } + + + /** + * Set (U, V) coords for the next vertex in the current shape. + * This is ugly as its own function, and will (almost?) always be + * coincident with a call to vertex. As of beta, this was moved to + * the protected method you see here, and called from an optional + * param of and overloaded vertex(). + *

+ * The parameters depend on the current textureMode. When using + * textureMode(IMAGE), the coordinates will be relative to the size + * of the image texture, when used with textureMode(NORMAL), + * they'll be in the range 0..1. + *

+ * Used by both PGraphics2D (for images) and PGraphics3D. + */ + protected void textureVertex(float u, float v) { + if (textureImage == null) { + throw new RuntimeException("need to set an image with texture() " + + "before using u and v coordinates"); + } + if (textureMode == IMAGE) { + u /= (float) textureImage.width; + v /= (float) textureImage.height; + } + + textureU = u; + textureV = v; + + if (textureU < 0) textureU = 0; + else if (textureU > 1) textureU = 1; + + if (textureV < 0) textureV = 0; + else if (textureV > 1) textureV = 1; + } + + + // eventually need to push a "default" setup down to this class + abstract public void vertex(float x, float y); + /* + splineVertexCount = 0; + //float vertex[]; + + if (vertexCount == vertices.length) { + float temp[][] = new float[vertexCount<<1][VERTEX_FIELD_COUNT]; + System.arraycopy(vertices, 0, temp, 0, vertexCount); + vertices = temp; + //message(CHATTER, "allocating more vertices " + vertices.length); + } + // not everyone needs this, but just easier to store rather + // than adding another moving part to the code... + vertices[vertexCount][MX] = x; + vertices[vertexCount][MY] = y; + vertexCount++; + + switch (shape) { + + case POINTS: + point(x, y); + break; + + case LINES: + if ((vertexCount % 2) == 0) { + line(vertices[vertexCount-2][MX], + vertices[vertexCount-2][MY], x, y); + } + break; + + case LINE_STRIP: + case LINE_LOOP: + if (vertexCount == 1) { + path = new Path(); + path.moveTo(x, y); + } else { + path.lineTo(x, y); + } + break; + + case TRIANGLES: + if ((vertexCount % 3) == 0) { + triangle(vertices[vertexCount - 3][MX], + vertices[vertexCount - 3][MY], + vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY], + x, y); + } + break; + + case TRIANGLE_STRIP: + if (vertexCount == 3) { + triangle(vertices[0][MX], vertices[0][MY], + vertices[1][MX], vertices[1][MY], + x, y); + } else if (vertexCount > 3) { + path = new Path(); + // when vertexCount == 4, draw an un-closed triangle + // for indices 2, 3, 1 + path.moveTo(vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY]); + path.lineTo(vertices[vertexCount - 1][MX], + vertices[vertexCount - 1][MY]); + path.lineTo(vertices[vertexCount - 3][MX], + vertices[vertexCount - 3][MY]); + draw_shape(path); + } + break; + + case TRIANGLE_FAN: + if (vertexCount == 3) { + triangle(vertices[0][MX], vertices[0][MY], + vertices[1][MX], vertices[1][MY], + x, y); + } else if (vertexCount > 3) { + path = new Path(); + // when vertexCount > 3, draw an un-closed triangle + // for indices 0 (center), previous, current + path.moveTo(vertices[0][MX], + vertices[0][MY]); + path.lineTo(vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY]); + path.lineTo(x, y); + draw_shape(path); + } + break; + + case QUADS: + if ((vertexCount % 4) == 0) { + quad(vertices[vertexCount - 4][MX], + vertices[vertexCount - 4][MY], + vertices[vertexCount - 3][MX], + vertices[vertexCount - 3][MY], + vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY], + x, y); + } + break; + + case QUAD_STRIP: + // 0---2---4 + // | | | + // 1---3---5 + if (vertexCount == 4) { + // note difference in winding order: + quad(vertices[0][MX], vertices[0][MY], + vertices[2][MX], vertices[2][MY], + x, y, + vertices[1][MX], vertices[1][MY]); + + } else if (vertexCount > 4) { + path = new Path(); + // when vertexCount == 5, draw an un-closed triangle + // for indices 2, 4, 5, 3 + path.moveTo(vertices[vertexCount - 3][MX], + vertices[vertexCount - 3][MY]); + path.lineTo(vertices[vertexCount - 1][MX], + vertices[vertexCount - 1][MY]); + path.lineTo(x, y); + path.lineTo(vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY]); + draw_shape(path); + } + break; + + case POLYGON: + //case CONCAVE_POLYGON: + //case CONVEX_POLYGON: + if (vertexCount == 1) { + path = new Path(); + path.moveTo(x, y); + } else { + path.lineTo(x, y); + } + break; + } + */ + + + abstract public void vertex(float x, float y, float z); + + + abstract public void vertex(float x, float y, float u, float v); + + + abstract public void vertex(float x, float y, float z, float u, float v); + + + public void bezierVertex(float x2, float y2, + float x3, float y3, + float x4, float y4) { + bezierVertex(x2, y2, Float.MAX_VALUE, + x3, y3, Float.MAX_VALUE, + x4, y4, Float.MAX_VALUE); + } + + + /** + * See notes with the bezier() function. + */ + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + if (shape != POLYGON) { + throw new RuntimeException("beginShape() and vertex() " + + "must be used before bezierVertex()"); + } + if (splineVertexCount > 0) { + float vertex[] = splineVertices[splineVertexCount-1]; + splineVertex(vertex[MX], vertex[MY], vertex[MZ], true); + + } else if (vertexCount > 0) { + // make sure there's at least a call to vertex() + float vertex[] = vertices[vertexCount-1]; + splineVertex(vertex[MX], vertex[MY], vertex[MZ], true); + + } else { + throw new RuntimeException("A call to vertex() must be used " + + "before bezierVertex()"); + } + splineVertex(x2, y2, z2, true); + splineVertex(x3, y3, z3, true); + splineVertex(x4, y4, z4, true); + } + + + /** + * See notes with the curve() function. + */ + public void curveVertex(float x, float y) { + splineVertex(x, y, Float.MAX_VALUE, false); + } + + + /** + * See notes with the curve() function. + */ + public void curveVertex(float x, float y, float z) { + splineVertex(x, y, z, false); + } + + + /** + * Implementation of generic spline vertex, will add coords to + * the splineVertices[] array and emit calls to draw segments + * as needed (every fourth point for bezier or every point starting + * with the fourth for catmull-rom). + * @param z z-coordinate, set to MAX_VALUE if it's 2D + * @param bezier true if it's a bezier instead of catmull-rom + */ + protected void splineVertex(float x, float y, float z, boolean bezier) { + // to improve processing applet load times, don't allocate + // space for the vertex data until actual use + if (splineVertices == null) { + splineVertices = new float[DEFAULT_SPLINE_VERTICES][VERTEX_FIELD_COUNT]; + } + + // if more than 128 points, shift everything back to the beginning + if (splineVertexCount == DEFAULT_SPLINE_VERTICES) { + System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES-3], 0, + splineVertices[0], 0, VERTEX_FIELD_COUNT); + System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES-2], 0, + splineVertices[1], 0, VERTEX_FIELD_COUNT); + System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES-1], 0, + splineVertices[2], 0, VERTEX_FIELD_COUNT); + splineVertexCount = 3; + } + + float vertex[] = splineVertices[splineVertexCount]; + + vertex[MX] = x; + vertex[MY] = y; + + if (fill) { + vertex[R] = fillR; + vertex[G] = fillG; + vertex[B] = fillB; + vertex[A] = fillA; + } + + if (stroke) { + vertex[SR] = strokeR; + vertex[SG] = strokeG; + vertex[SB] = strokeB; + vertex[SA] = strokeA; + vertex[SW] = strokeWeight; + } + + if (textureImage != null) { + vertex[U] = textureU; + vertex[V] = textureV; + } + + // when the coords are Float.MAX_VALUE, then treat as a 2D curve + int dimensions = (z == Float.MAX_VALUE) ? 2 : 3; + + if (dimensions == 3) { + vertex[MZ] = z; + + vertex[NX] = normalX; + vertex[NY] = normalY; + vertex[NZ] = normalZ; + } + + splineVertexCount++; + + // draw a segment if there are enough points + if (splineVertexCount > 3) { + if (bezier) { + if ((splineVertexCount % 4) == 0) { + if (!bezierInited) bezierInit(); + splineSegment(splineVertexCount-4, + splineVertexCount-4, + bezierDrawMatrix, dimensions, + bezierDetail); + } + } else { // catmull-rom curve (!bezier) + if (!curve_inited) curve_init(); + splineSegment(splineVertexCount-4, + splineVertexCount-3, + curve_draw, dimensions, + curveDetail); + } + } + } + + + /** This feature is in testing, do not use or rely upon its implementation */ + public void breakShape() { + } + + + public final void endShape() { + endShape(OPEN); + } + + + abstract public void endShape(int mode); + + + + ////////////////////////////////////////////////////////////// + + // COMPOUND PATHS + + + /** + * Begin a new path. This can be used after beginShape() to draw + * a compound path (i.e. to draw shape with a hole on the interior) + * For instance, to draw a shape that has a hole in its interior, + * the format would be: + *

+   * beginShape();
+   * beginPath();
+   * // multiple calls to vertex() that draw the exterior shape
+   * endPath();
+   * beginPath();
+   * // several calls to vertex() to draw the interior hole
+   * endPath();
+   * // more beginPath/endPath pairs can be used for additional holes
+   * endShape();
+   * 
+ *

+ * This will probably be available only with the OpenGL renderer, + * because it has a built-in tesselator from GLU. + */ + //public void beginPath() { + //throw new RuntimeException("beginPath() is not available"); + //} + + + /** + * End a path. Use this with beginPath() to close out a compound path. + *

+ * This will probably be available only with the OpenGL renderer, + * because it has a built-in tesselator from GLU. + */ + //public void endPath() { + //throw new RuntimeException("endPath() is not available"); + //} + + + + ////////////////////////////////////////////////////////////// + + // SIMPLE SHAPES WITH ANALOGUES IN beginShape() + + + public void point(float x, float y) { + beginShape(POINTS); + vertex(x, y); + endShape(); + } + + + public void point(float x, float y, float z) { + beginShape(POINTS); + vertex(x, y, z); + endShape(); + } + + + public void line(float x1, float y1, float x2, float y2) { + beginShape(LINES); + vertex(x1, y1); + vertex(x2, y2); + endShape(); + } + + + public void line(float x1, float y1, float z1, + float x2, float y2, float z2) { + beginShape(LINES); + vertex(x1, y1, z1); + vertex(x2, y2, z2); + endShape(); + } + + + public void triangle(float x1, float y1, float x2, float y2, + float x3, float y3) { + beginShape(TRIANGLES); + vertex(x1, y1); + vertex(x2, y2); + vertex(x3, y3); + endShape(); + } + + + public void quad(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + beginShape(QUADS); + vertex(x1, y1); + vertex(x2, y2); + vertex(x3, y3); + vertex(x4, y4); + endShape(); + } + + + + ////////////////////////////////////////////////////////////// + + // RECT + + + public void rectMode(int mode) { + rectMode = mode; + } + + + public void rect(float x1, float y1, float x2, float y2) { + float hradius, vradius; + switch (rectMode) { + case CORNERS: + break; + case CORNER: + x2 += x1; y2 += y1; + break; + case RADIUS: + hradius = x2; + vradius = y2; + x2 = x1 + hradius; + y2 = y1 + vradius; + x1 -= hradius; + y1 -= vradius; + break; + case CENTER: + hradius = x2 / 2.0f; + vradius = y2 / 2.0f; + x2 = x1 + hradius; + y2 = y1 + vradius; + x1 -= hradius; + y1 -= vradius; + } + + if (x1 > x2) { + float temp = x1; x1 = x2; x2 = temp; + } + + if (y1 > y2) { + float temp = y1; y1 = y2; y2 = temp; + } + + rectImpl(x1, y1, x2, y2); + } + + + protected void rectImpl(float x1, float y1, float x2, float y2) { + quad(x1, y1, x2, y1, x2, y2, x1, y2); + } + + + + ////////////////////////////////////////////////////////////// + + // ELLIPSE AND ARC + + + public void ellipseMode(int mode) { + ellipseMode = mode; + } + + + public void ellipse(float a, float b, float c, float d) { + float x = a; + float y = b; + float w = c; + float h = d; + + if (ellipseMode == CORNERS) { + w = c - a; + h = d - b; + + } else if (ellipseMode == RADIUS) { + x = a - c; + y = b - d; + w = c * 2; + h = d * 2; + + } else if (ellipseMode == CENTER) { + x = a - c/2f; + y = b - d/2f; + } + + if (w < 0) { // undo negative width + x += w; + w = -w; + } + + if (h < 0) { // undo negative height + y += h; + h = -h; + } + + ellipseImpl(x, y, w, h); + } + + + protected void ellipseImpl(float x1, float y1, float w, float h) { + float hradius = w / 2f; + float vradius = h / 2f; + + float centerX = x1 + hradius; + float centerY = y1 + vradius; + + // adapt accuracy to radii used w/ a minimum of 4 segments [toxi] + // now uses current scale factors to determine "real" transformed radius + + //int cAccuracy = (int)(4+Math.sqrt(hradius*abs(m00)+vradius*abs(m11))*2); + //int cAccuracy = (int)(4+Math.sqrt(hradius+vradius)*2); + + // notched this up to *3 instead of *2 because things were + // looking a little rough, i.e. the calculate->arctangent example [fry] + + // also removed the m00 and m11 because those were causing weirdness + // need an actual measure of magnitude in there [fry] + + int accuracy = (int)(4+Math.sqrt(hradius+vradius)*3); + //System.out.println("accuracy is " + accuracy); + + // [toxi031031] adapted to use new lookup tables + float inc = (float)SINCOS_LENGTH / accuracy; + + float val = 0; + /* + beginShape(POLYGON); + for (int i = 0; i < cAccuracy; i++) { + vertex(centerX + cosLUT[(int) val] * hradius, + centerY + sinLUT[(int) val] * vradius); + val += inc; + } + endShape(); + */ + + if (fill) { + boolean savedStroke = stroke; + stroke = false; + + beginShape(TRIANGLE_FAN); + normal(0, 0, 1); + vertex(centerX, centerY); + for (int i = 0; i < accuracy; i++) { + vertex(centerX + cosLUT[(int) val] * hradius, + centerY + sinLUT[(int) val] * vradius); + val += inc; + } + // back to the beginning + vertex(centerX + cosLUT[0] * hradius, + centerY + sinLUT[0] * vradius); + endShape(); + + stroke = savedStroke; + } + + if (stroke) { + boolean savedFill = fill; + fill = false; + + val = 0; + beginShape(); //LINE_LOOP); + for (int i = 0; i < accuracy; i++) { + vertex(centerX + cosLUT[(int) val] * hradius, + centerY + sinLUT[(int) val] * vradius); + val += inc; + } + endShape(CLOSE); + + fill = savedFill; + } + } + + + /** + * Identical parameters and placement to ellipse, + * but draws only an arc of that ellipse. + *

+ * start and stop are always radians because angleMode() was goofy. + * ellipseMode() sets the placement. + *

+ * also tries to be smart about start < stop. + */ + public void arc(float a, float b, float c, float d, + float start, float stop) { + float x = a; + float y = b; + float w = c; + float h = d; + + if (ellipseMode == CORNERS) { + w = c - a; + h = d - b; + + } else if (ellipseMode == RADIUS) { + x = a - c; + y = b - d; + w = c * 2; + h = d * 2; + + } else if (ellipseMode == CENTER) { + x = a - c/2f; + y = b - d/2f; + } + + //if (angleMode == DEGREES) { + //start = start * DEG_TO_RAD; + //stop = stop * DEG_TO_RAD; + //} + // before running a while loop like this, + // make sure it will exit at some point. + if (Float.isInfinite(start) || Float.isInfinite(stop)) return; + while (stop < start) stop += TWO_PI; + + arcImpl(x, y, w, h, start, stop); + } + + + /** + * Start and stop are in radians, converted by the parent function. + * Note that the radians can be greater (or less) than TWO_PI. + * This is so that an arc can be drawn that crosses zero mark, + * and the user will still collect $200. + */ + protected void arcImpl(float x1, float y1, float w, float h, + float start, float stop) { + float hr = w / 2f; + float vr = h / 2f; + + float centerX = x1 + hr; + float centerY = y1 + vr; + + if (fill) { + // shut off stroke for a minute + boolean savedStroke = stroke; + stroke = false; + + int startLUT = (int) (0.5f + (start / TWO_PI) * SINCOS_LENGTH); + int stopLUT = (int) (0.5f + (stop / TWO_PI) * SINCOS_LENGTH); + + beginShape(TRIANGLE_FAN); + vertex(centerX, centerY); + int increment = 1; // what's a good algorithm? stopLUT - startLUT; + for (int i = startLUT; i < stopLUT; i += increment) { + int ii = i % SINCOS_LENGTH; + vertex(centerX + cosLUT[ii] * hr, + centerY + sinLUT[ii] * vr); + } + // draw last point explicitly for accuracy + vertex(centerX + cosLUT[stopLUT % SINCOS_LENGTH] * hr, + centerY + sinLUT[stopLUT % SINCOS_LENGTH] * vr); + endShape(); + + stroke = savedStroke; + } + + if (stroke) { + // Almost identical to above, but this uses a LINE_STRIP + // and doesn't include the first (center) vertex. + + boolean savedFill = fill; + fill = false; + + int startLUT = (int) (0.5f + (start / TWO_PI) * SINCOS_LENGTH); + int stopLUT = (int) (0.5f + (stop / TWO_PI) * SINCOS_LENGTH); + + beginShape(); //LINE_STRIP); + int increment = 1; // what's a good algorithm? stopLUT - startLUT; + for (int i = startLUT; i < stopLUT; i += increment) { + int ii = i % SINCOS_LENGTH; + vertex(centerX + cosLUT[ii] * hr, + centerY + sinLUT[ii] * vr); + } + // draw last point explicitly for accuracy + vertex(centerX + cosLUT[stopLUT % SINCOS_LENGTH] * hr, + centerY + sinLUT[stopLUT % SINCOS_LENGTH] * vr); + endShape(); + + fill = savedFill; + } + } + + + + ////////////////////////////////////////////////////////////// + + // BOX & SPHERE + + + public void box(float size) { + depthError("box"); + } + + public void box(float w, float h, float d) { + depthError("box"); + } + + public void sphereDetail(int res) { + depthError("sphereDetail"); + } + + public void sphere(float r) { + depthError("sphere"); + } + + + + ////////////////////////////////////////////////////////////// + + // BEZIER + + + /** + * Evalutes quadratic bezier at point t for points a, b, c, d. + * t varies between 0 and 1, and a and d are the on curve points, + * b and c are the control points. this can be done once with the + * x coordinates and a second time with the y coordinates to get + * the location of a bezier curve at t. + *

+ * For instance, to convert the following example:

+   * stroke(255, 102, 0);
+   * line(85, 20, 10, 10);
+   * line(90, 90, 15, 80);
+   * stroke(0, 0, 0);
+   * bezier(85, 20, 10, 10, 90, 90, 15, 80);
+   *
+   * // draw it in gray, using 10 steps instead of the default 20
+   * // this is a slower way to do it, but useful if you need
+   * // to do things with the coordinates at each step
+   * stroke(128);
+   * beginShape(LINE_STRIP);
+   * for (int i = 0; i <= 10; i++) {
+   *   float t = i / 10.0f;
+   *   float x = bezierPoint(85, 10, 90, 15, t);
+   *   float y = bezierPoint(20, 10, 90, 80, t);
+   *   vertex(x, y);
+   * }
+   * endShape();
+ */ + public float bezierPoint(float a, float b, float c, float d, float t) { + float t1 = 1.0f - t; + return a*t1*t1*t1 + 3*b*t*t1*t1 + 3*c*t*t*t1 + d*t*t*t; + } + + + /** + * Provide the tangent at the given point on the bezier curve. + * Based on code from v3ga's wordstree sketch. + */ + public float bezierTangent(float a, float b, float c, float d, float t) { + float t1 = 1.0f - t; + + return (a * 3 * t*t + + b * 3 * t * (2 - 3*t) + + c * 3 * (3*t*t - 4*t + 1) + + d * -3 * t1*t1); + } + + + protected void bezierInit() { + bezierDetail(bezierDetail); + } + + + public void bezierDetail(int detail) { + if (bezierForwardMatrix == null) { + bezierForwardMatrix = new float[4][4]; + bezierDrawMatrix = new float[4][4]; + } + bezierDetail = detail; + bezierInited = true; + + // setup matrix for forward differencing to speed up drawing + setup_spline_forward(detail, bezierForwardMatrix); + + // multiply the basis and forward diff matrices together + // saves much time since this needn't be done for each curve + mult_spline_matrix(bezierForwardMatrix, bezier_basis, bezierDrawMatrix, 4); + } + + + /** + * Draw a cubic bezier curve. The first and last points are + * the on-curve points. The middle two are the 'control' points, + * or 'handles' in an application like Illustrator. + *

+ * Identical to typing: + *

beginShape();
+   * vertex(x1, y1);
+   * bezierVertex(x2, y2, x3, y3, x4, y4);
+   * endShape();
+   * 
+ * In Postscript-speak, this would be: + *
moveto(x1, y1);
+   * curveto(x2, y2, x3, y3, x4, y4);
+ * If you were to try and continue that curve like so: + *
curveto(x5, y5, x6, y6, x7, y7);
+ * This would be done in processing by adding these statements: + *
bezierVertex(x5, y5, x6, y6, x7, y7)
+   * 
+ * To draw a quadratic (instead of cubic) curve, + * use the control point twice by doubling it: + *
bezier(x1, y1, cx, cy, cx, cy, x2, y2);
+ */ + public void bezier(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + beginShape(); //LINE_STRIP); + vertex(x1, y1); + bezierVertex(x2, y2, x3, y3, x4, y4); + endShape(); + } + + + public void bezier(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + beginShape(); //LINE_STRIP); + vertex(x1, y1, z1); + bezierVertex(x2, y2, z2, + x3, y3, z3, + x4, y4, z4); + endShape(); + } + + + + ////////////////////////////////////////////////////////////// + + // CATMULL-ROM CURVE + + + /** + * Get a location along a catmull-rom curve segment. + * + * @param t Value between zero and one for how far along the segment + */ + public float curvePoint(float a, float b, float c, float d, float t) { + if (!curve_inited) curve_init(); + + float tt = t * t; + float ttt = t * tt; + float m[][] = curve_basis; + + // not optimized (and probably need not be) + return (a * (ttt*m[0][0] + tt*m[1][0] + t*m[2][0] + m[3][0]) + + b * (ttt*m[0][1] + tt*m[1][1] + t*m[2][1] + m[3][1]) + + c * (ttt*m[0][2] + tt*m[1][2] + t*m[2][2] + m[3][2]) + + d * (ttt*m[0][3] + tt*m[1][3] + t*m[2][3] + m[3][3])); + } + + + public float curveTangent(float a, float b, float c, float d, + float t) { + System.err.println("curveTangent not yet implemented"); + return 0; + } + + + public void curveDetail(int detail) { + curve_mode(detail, curveTightness); + } + + + public void curveTightness(float tightness) { + curve_mode(curveDetail, tightness); + } + + + protected void curve_init() { + curve_mode(curveDetail, curveTightness); + } + + + /** + * Set the number of segments to use when drawing a Catmull-Rom + * curve, and setting the s parameter, which defines how tightly + * the curve fits to each vertex. Catmull-Rom curves are actually + * a subset of this curve type where the s is set to zero. + *

+ * (This function is not optimized, since it's not expected to + * be called all that often. there are many juicy and obvious + * opimizations in here, but it's probably better to keep the + * code more readable) + */ + protected void curve_mode(int segments, float s) { + curveDetail = segments; + + if (curve_basis == null) { + // allocate these when used, to save startup time + curve_basis = new float[4][4]; + curve_forward = new float[4][4]; + curve_draw = new float[4][4]; + curve_inited = true; + } + + float c[][] = curve_basis; + + c[0][0] = s-1; c[0][1] = s+3; c[0][2] = -3-s; c[0][3] = 1-s; + c[1][0] = 2*(1-s); c[1][1] = -5-s; c[1][2] = 2*(s+2); c[1][3] = s-1; + c[2][0] = s-1; c[2][1] = 0; c[2][2] = 1-s; c[2][3] = 0; + c[3][0] = 0; c[3][1] = 2; c[3][2] = 0; c[3][3] = 0; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + c[i][j] /= 2f; + } + } + setup_spline_forward(segments, curve_forward); + + if (bezierBasisInverse == null) { + bezierBasisInverse = new PMatrix(bezierBasis).invert(); + } + + // hack here to get PGraphics2 working + curveToBezierMatrix = new PMatrix(c[0][0], c[0][1], c[0][2], c[0][3], + c[1][0], c[1][1], c[1][2], c[1][3], + c[2][0], c[2][1], c[2][2], c[2][3], + c[3][0], c[3][1], c[3][2], c[3][3]); + curveToBezierMatrix.preApply(bezierBasisInverse); + + // multiply the basis and forward diff matrices together + // saves much time since this needn't be done for each curve + mult_spline_matrix(curve_forward, curve_basis, curve_draw, 4); + } + + + /** + * Draws a segment of Catmull-Rom curve. + *

+ * As of 0070, this function no longer doubles the first and + * last points. The curves are a bit more boring, but it's more + * mathematically correct, and properly mirrored in curvePoint(). + *

+ * Identical to typing out:

+   * beginShape();
+   * curveVertex(x1, y1);
+   * curveVertex(x2, y2);
+   * curveVertex(x3, y3);
+   * curveVertex(x4, y4);
+   * endShape();
+   * 
+ */ + public void curve(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + beginShape(); //LINE_STRIP); + curveVertex(x1, y1); + curveVertex(x2, y2); + curveVertex(x3, y3); + curveVertex(x4, y4); + endShape(); + } + + + public void curve(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + beginShape(); //LINE_STRIP); + curveVertex(x1, y1, z1); + curveVertex(x2, y2, z2); + curveVertex(x3, y3, z3); + curveVertex(x4, y4, z4); + endShape(); + } + + + + ////////////////////////////////////////////////////////////// + + // SPLINE UTILITY FUNCTIONS (used by both Bezier and Catmull-Rom) + + + /** + * Setup forward-differencing matrix to be used for speedy + * curve rendering. It's based on using a specific number + * of curve segments and just doing incremental adds for each + * vertex of the segment, rather than running the mathematically + * expensive cubic equation. + * @param segments number of curve segments to use when drawing + */ + protected void setup_spline_forward(int segments, float fwd[][]) { + float f = 1.0f / segments; + float ff = f * f; + float fff = ff * f; + + fwd[0][0] = 0; fwd[0][1] = 0; fwd[0][2] = 0; fwd[0][3] = 1; + fwd[1][0] = fff; fwd[1][1] = ff; fwd[1][2] = f; fwd[1][3] = 0; + fwd[2][0] = 6*fff; fwd[2][1] = 2*ff; fwd[2][2] = 0; fwd[2][3] = 0; + fwd[3][0] = 6*fff; fwd[3][1] = 0; fwd[3][2] = 0; fwd[3][3] = 0; + } + + + // internal matrix multiplication routine used by the spline code + // should these go to 4 instead of 3? + //void mult_curve_matrix(float m[4][4], float g[4][3], float mg[4][3]); + protected void mult_spline_matrix(float m[][], float g[][], + float mg[][], int dimensions) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < dimensions; j++) { + mg[i][j] = 0; + } + } + for (int i = 0; i < 4; i++) { + for (int j = 0; j < dimensions; j++) { + for (int k = 0; k < 4; k++) { + mg[i][j] = mg[i][j] + (m[i][k] * g[k][j]); + } + } + } + } + + + /** + * Draw a segment of spline (bezier or catmull-rom curve) + * using the matrix m, which is the basis matrix already + * multiplied with the forward differencing matrix. + *

+ * the x0, y0, z0 points are the point that's being used as + * the start, and also as the accumulator. for bezier curves, + * the x1, y1, z1 are the first point drawn, and added to. + * for catmull-rom curves, the first control point (x2, y2, z2) + * is the first drawn point, and is accumulated to. + */ + protected void splineSegment(int offset, int start, float m[][], + int dimensions, int segments) { + float x1 = splineVertices[offset+0][MX]; + float x2 = splineVertices[offset+1][MX]; + float x3 = splineVertices[offset+2][MX]; + float x4 = splineVertices[offset+3][MX]; + float x0 = splineVertices[start][MX]; + + float y1 = splineVertices[offset+0][MY]; + float y2 = splineVertices[offset+1][MY]; + float y3 = splineVertices[offset+2][MY]; + float y4 = splineVertices[offset+3][MY]; + float y0 = splineVertices[start][MY]; + + float xplot1 = m[1][0]*x1 + m[1][1]*x2 + m[1][2]*x3 + m[1][3]*x4; + float xplot2 = m[2][0]*x1 + m[2][1]*x2 + m[2][2]*x3 + m[2][3]*x4; + float xplot3 = m[3][0]*x1 + m[3][1]*x2 + m[3][2]*x3 + m[3][3]*x4; + + float yplot1 = m[1][0]*y1 + m[1][1]*y2 + m[1][2]*y3 + m[1][3]*y4; + float yplot2 = m[2][0]*y1 + m[2][1]*y2 + m[2][2]*y3 + m[2][3]*y4; + float yplot3 = m[3][0]*y1 + m[3][1]*y2 + m[3][2]*y3 + m[3][3]*y4; + + // vertex() will reset splineVertexCount, so save it + int cvertexSaved = splineVertexCount; + + if (dimensions == 3) { + float z1 = splineVertices[offset+0][MZ]; + float z2 = splineVertices[offset+1][MZ]; + float z3 = splineVertices[offset+2][MZ]; + float z4 = splineVertices[offset+3][MZ]; + float z0 = splineVertices[start][MZ]; + + float zplot1 = m[1][0]*z1 + m[1][1]*z2 + m[1][2]*z3 + m[1][3]*z4; + float zplot2 = m[2][0]*z1 + m[2][1]*z2 + m[2][2]*z3 + m[2][3]*z4; + float zplot3 = m[3][0]*z1 + m[3][1]*z2 + m[3][2]*z3 + m[3][3]*z4; + + vertex(x0, y0, z0); + for (int j = 0; j < segments; j++) { + x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z0 += zplot1; zplot1 += zplot2; zplot2 += zplot3; + vertex(x0, y0, z0); + } + } else { + vertex(x0, y0); + for (int j = 0; j < segments; j++) { + x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + vertex(x0, y0); + } + } + splineVertexCount = cvertexSaved; + } + + + /* + protected void spline2_segment(int offset, int start, + float m[][], int segments) { + float x1 = splineVertices[offset][MX]; + float y1 = splineVertices[offset][MY]; + + float x2 = splineVertices[offset+1][MX]; + float y2 = splineVertices[offset+1][MY]; + + float x3 = splineVertices[offset+2][MX]; + float y3 = splineVertices[offset+2][MY]; + + float x4 = splineVertices[offset+3][MX]; + float y4 = splineVertices[offset+3][MY]; + + float x0 = splineVertices[start][MX]; + float y0 = splineVertices[start][MY]; + + float xplot1 = m[1][0]*x1 + m[1][1]*x2 + m[1][2]*x3 + m[1][3]*x4; + float xplot2 = m[2][0]*x1 + m[2][1]*x2 + m[2][2]*x3 + m[2][3]*x4; + float xplot3 = m[3][0]*x1 + m[3][1]*x2 + m[3][2]*x3 + m[3][3]*x4; + + float yplot1 = m[1][0]*y1 + m[1][1]*y2 + m[1][2]*y3 + m[1][3]*y4; + float yplot2 = m[2][0]*y1 + m[2][1]*y2 + m[2][2]*y3 + m[2][3]*y4; + float yplot3 = m[3][0]*y1 + m[3][1]*y2 + m[3][2]*y3 + m[3][3]*y4; + + // vertex() will reset splineVertexCount, so save it + int splineVertexSaved = splineVertexCount; + vertex(x0, y0); + for (int j = 0; j < segments; j++) { + x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + vertex(x0, y0); + } + splineVertexCount = splineVertexSaved; + } + + + protected void spline3_segment(int offset, int start, + float m[][], int segments) { + float x1 = splineVertices[offset+0][MX]; + float y1 = splineVertices[offset+0][MY]; + float z1 = splineVertices[offset+0][MZ]; + + float x2 = splineVertices[offset+1][MX]; + float y2 = splineVertices[offset+1][MY]; + float z2 = splineVertices[offset+1][MZ]; + + float x3 = splineVertices[offset+2][MX]; + float y3 = splineVertices[offset+2][MY]; + float z3 = splineVertices[offset+2][MZ]; + + float x4 = splineVertices[offset+3][MX]; + float y4 = splineVertices[offset+3][MY]; + float z4 = splineVertices[offset+3][MZ]; + + float x0 = splineVertices[start][MX]; + float y0 = splineVertices[start][MY]; + float z0 = splineVertices[start][MZ]; + + float xplot1 = m[1][0]*x1 + m[1][1]*x2 + m[1][2]*x3 + m[1][3]*x4; + float xplot2 = m[2][0]*x1 + m[2][1]*x2 + m[2][2]*x3 + m[2][3]*x4; + float xplot3 = m[3][0]*x1 + m[3][1]*x2 + m[3][2]*x3 + m[3][3]*x4; + + float yplot1 = m[1][0]*y1 + m[1][1]*y2 + m[1][2]*y3 + m[1][3]*y4; + float yplot2 = m[2][0]*y1 + m[2][1]*y2 + m[2][2]*y3 + m[2][3]*y4; + float yplot3 = m[3][0]*y1 + m[3][1]*y2 + m[3][2]*y3 + m[3][3]*y4; + + float zplot1 = m[1][0]*z1 + m[1][1]*z2 + m[1][2]*z3 + m[1][3]*z4; + float zplot2 = m[2][0]*z1 + m[2][1]*z2 + m[2][2]*z3 + m[2][3]*z4; + float zplot3 = m[3][0]*z1 + m[3][1]*z2 + m[3][2]*z3 + m[3][3]*z4; + + // vertex() will reset splineVertexCount, so save it + int cvertexSaved = splineVertexCount; + vertex(x0, y0, z0); + for (int j = 0; j < segments; j++) { + x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z0 += zplot1; zplot1 += zplot2; zplot2 += zplot3; + vertex(x0, y0, z0); + } + splineVertexCount = cvertexSaved; + } + */ + + + + ////////////////////////////////////////////////////////////// + + // IMAGE + + + public void image(PImage image, float x, float y) { + imageImpl(image, + x, y, x+image.width, y+image.height, + 0, 0, image.width, image.height); + } + + + public void image(PImage image, + float x, float y, float c, float d) { + image(image, x, y, c, d, 0, 0, image.width, image.height); + } + + + /** + * u, v coordinates are always based on image space location, + * regardless of the current textureMode(). + */ + public void image(PImage image, + float a, float b, float c, float d, + int u1, int v1, int u2, int v2) { + if (imageMode == CORNER) { + if (c < 0) { // reset a negative width + a += c; c = -c; + } + if (d < 0) { // reset a negative height + b += d; d = -d; + } + + imageImpl(image, + a, b, a + c, b + d, + u1, v1, u2, v2); + + } else if (imageMode == CORNERS) { + if (c < a) { // reverse because x2 < x1 + float temp = a; a = c; c = temp; + } + if (d < b) { // reverse because y2 < y1 + float temp = b; b = d; d = temp; + } + + imageImpl(image, + a, b, c, d, + u1, v1, u2, v2); + } + } + + + /** + * Expects x1, y1, x2, y2 coordinates where (x2 >= x1) and (y2 >= y1). + * If tint() has been called, the image will be colored. + *

+ * The default implementation draws an image as a textured quad. + * The (u, v) coordinates are in image space (they're ints, after all..) + */ + protected void imageImpl(PImage image, + float x1, float y1, float x2, float y2, + int u1, int v1, int u2, int v2) { + boolean savedStroke = stroke; + boolean savedFill = fill; + int savedTextureMode = textureMode; + + stroke = false; + fill = true; + textureMode = IMAGE; + + float savedFillR = fillR; + float savedFillG = fillG; + float savedFillB = fillB; + float savedFillA = fillA; + + if (tint) { + fillR = tintR; + fillG = tintG; + fillB = tintB; + fillA = tintA; + + } else { + fillR = 1; + fillG = 1; + fillB = 1; + fillA = 1; + } + + //System.out.println(fill + " " + fillR + " " + fillG + " " + fillB); + + beginShape(QUADS); + texture(image); + vertex(x1, y1, u1, v1); + vertex(x1, y2, u1, v2); + vertex(x2, y2, u2, v2); + vertex(x2, y1, u2, v1); + endShape(); + + stroke = savedStroke; + fill = savedFill; + textureMode = savedTextureMode; + + fillR = savedFillR; + fillG = savedFillG; + fillB = savedFillB; + fillA = savedFillA; + } + + + + ////////////////////////////////////////////////////////////// + + // TEXT/FONTS + + + /** + * Sets the alignment of the text to one of LEFT, CENTER, or RIGHT. + * This will also reset the vertical text alignment to BASELINE. + */ + public void textAlign(int align) { + textAlign(align, BASELINE); + } + + + /** + * Sets the horizontal and vertical alignment of the text. The horizontal + * alignment can be one of LEFT, CENTER, or RIGHT. The vertical alignment + * can be TOP, BOTTOM, CENTER, or the BASELINE (the default). + */ + public void textAlign(int alignX, int alignY) { + textAlign = alignX; + textAlignY = alignY; + } + + + /** + * Returns the ascent of the current font at the current size. + * This is a method, rather than a variable inside the PGraphics object + * because it requires calculation. + */ + public float textAscent() { + if (textFont == null) { + throw new RuntimeException("use textFont() before textAscent()"); + } + + return textFont.ascent() * + ((textMode == SCREEN) ? textFont.size : textSize); + } + + + /** + * Returns the descent of the current font at the current size. + * This is a method, rather than a variable inside the PGraphics object + * because it requires calculation. + */ + public float textDescent() { + if (textFont != null) { + return textFont.descent() * + ((textMode == SCREEN) ? textFont.size : textSize); + + } else { + throw new RuntimeException("use textFont() before textDescent()"); + } + } + + + /** + * Sets the current font. The font's size will be the "natural" + * size of this font (the size that was set when using "Create Font"). + * The leading will also be reset. + */ + public void textFont(PFont which) { + if (which != null) { + textFont = which; + if (hints[ENABLE_NATIVE_FONTS]) { + if (which.font == null) { + which.findFont(); + } + } + textFontNative = which.font; + + //textFontNativeMetrics = null; + // changed for rev 0104 for textMode(SHAPE) in opengl + if (textFontNative != null) { + // TODO need a better way to handle this. could use reflection to get + // rid of the warning, but that'd be a little silly. supporting this is + // an artifact of supporting java 1.1, otherwise we'd use getLineMetrics, + // as recommended by the @deprecated flag. + textFontNativeMetrics = + Toolkit.getDefaultToolkit().getFontMetrics(textFontNative); + } + textSize(which.size); + + } else { + throw new RuntimeException("a null PFont was passed to textFont()"); + } + } + + + /** + * Useful function to set the font and size at the same time. + */ + public void textFont(PFont which, float size) { + textFont(which); + textSize(size); + } + + + /** + * Set the text leading to a specific value. If using a custom + * value for the text leading, you'll have to call textLeading() + * again after any calls to textSize(). + */ + public void textLeading(float leading) { + textLeading = leading; + } + + + /** + * Sets the text rendering/placement to be either SCREEN (direct + * to the screen, exact coordinates, only use the font's original size) + * or MODEL (the default, where text is manipulated by translate() and + * can have a textSize). The text size cannot be set when using + * textMode(SCREEN), because it uses the pixels directly from the font. + */ + public void textMode(int mode) { + // CENTER and MODEL overlap (they're both 3) + if ((mode == LEFT) || (mode == RIGHT)) { + throw new RuntimeException("textMode() is now textAlign() " + + "in Processing beta"); + } + if ((mode != SCREEN) && (mode != MODEL)) { + throw new RuntimeException("Only textMode(SCREEN) and textMode(MODEL) " + + "are available with this renderer."); + } + + //if (textFont != null) { + textMode = mode; + + // reset the font to its natural size + // (helps with width calculations and all that) + //if (textMode == SCREEN) { + //textSize(textFont.size); + //} + + //} else { + //throw new RuntimeException("use textFont() before textMode()"); + //} + } + + + /** + * Sets the text size, also resets the value for the leading. + */ + public void textSize(float size) { + if (textFont != null) { + if ((textMode == SCREEN) && (size != textFont.size)) { + throw new RuntimeException("textSize() cannot be used with " + + "textMode(SCREEN)"); + } + textSize = size; + //textLeading = textSize * + // ((textFont.ascent() + textFont.descent()) * 1.275f); + textLeading = (textAscent() + textDescent()) * 1.275f; + + } else { + throw new RuntimeException("Use textFont() before textSize()"); + } + } + + + // ........................................................ + + + public float textWidth(char c) { + textBuffer[0] = c; + return textWidthImpl(textBuffer, 0, 1); + } + + + /** + * Return the width of a line of text. If the text has multiple + * lines, this returns the length of the longest line. + */ + public float textWidth(String str) { + if (textFont == null) { + throw new RuntimeException("use textFont() before textWidth()"); + } + + int length = str.length(); + if (length > textWidthBuffer.length) { + textWidthBuffer = new char[length + 10]; + } + str.getChars(0, length, textWidthBuffer, 0); + + float wide = 0; + int index = 0; + int start = 0; + + while (index < length) { + if (textWidthBuffer[index] == '\n') { + wide = Math.max(wide, textWidthImpl(textWidthBuffer, start, index)); + start = index+1; + } + index++; + } + if (start < length) { + wide = Math.max(wide, textWidthImpl(textWidthBuffer, start, index)); + } + return wide; + } + + + /** + * Implementation of returning the text width of + * the chars [start, stop) in the buffer. + * Unlike the previous version that was inside PFont, this will + * return the size not of a 1 pixel font, but the actual current size. + */ + protected float textWidthImpl(char buffer[], int start, int stop) { + float wide = 0; + for (int i = start; i < stop; i++) { + // could add kerning here, but it just ain't implemented + wide += textFont.width(buffer[i]) * textSize; + } + return wide; + } + + + // ........................................................ + + + /** + * Write text where we just left off. + */ + public void text(char c) { + text(c, textX, textY, textZ); + } + + + /** + * Draw a single character on screen. + * Extremely slow when used with textMode(SCREEN) and Java 2D, + * because loadPixels has to be called first and updatePixels last. + */ + public void text(char c, float x, float y) { + if (textFont == null) { + throw new RuntimeException("use textFont() before text()"); + } + + if (textMode == SCREEN) loadPixels(); + + textBuffer[0] = c; + textLineImpl(textBuffer, 0, 1, x, y); + + if (textMode == SCREEN) updatePixels(); + } + + + /** + * Draw a single character on screen (with a z coordinate) + */ + public void text(char c, float x, float y, float z) { + if ((z != 0) && (textMode == SCREEN)) { + String msg = "textMode(SCREEN) cannot have a z coordinate"; + throw new RuntimeException(msg); + } + + if (z != 0) translate(0, 0, z); // slowness, badness + + text(c, x, y); + textZ = z; + + if (z != 0) translate(0, 0, -z); + } + + + /** + * Write text where we just left off. + */ + public void text(String str) { + text(str, textX, textY, textZ); + } + + + /** + * Draw a chunk of text. + * Newlines that are \n (Unix newline or linefeed char, ascii 10) + * are honored, but \r (carriage return, Windows and Mac OS) are + * ignored. + */ + public void text(String str, float x, float y) { + if (textFont == null) { + throw new RuntimeException("use textFont() before text()"); + } + + if (textMode == SCREEN) loadPixels(); + + int length = str.length(); + if (length > textBuffer.length) { + textBuffer = new char[length + 10]; + } + str.getChars(0, length, textBuffer, 0); + + // If multiple lines, sum the height of the additional lines + float high = 0; //-textAscent(); + for (int i = 0; i < length; i++) { + if (textBuffer[i] == '\n') { + high += textLeading; + } + } + if (textAlignY == CENTER) { + // for a single line, this adds half the textAscent to y + // for multiple lines, subtract half the additional height + //y += (textAscent() - textDescent() - high)/2; + y += (textAscent() - high)/2; + } else if (textAlignY == TOP) { + // for a single line, need to add textAscent to y + // for multiple lines, no different + y += textAscent(); + } else if (textAlignY == BOTTOM) { + // for a single line, this is just baseline (unchanged) + // for multiple lines, subtract leading for each line + y -= high; + //} else if (textAlignY == BASELINE) { + // do nothing + } + + int start = 0; + int index = 0; + while (index < length) { + if (textBuffer[index] == '\n') { + textLineImpl(textBuffer, start, index, x, y); + start = index + 1; + y += textLeading; + } + index++; + } + if (start < length) { + textLineImpl(textBuffer, start, index, x, y); + } + if (textMode == SCREEN) updatePixels(); + } + + + /** + * Same as above but with a z coordinate. + */ + public void text(String str, float x, float y, float z) { + if ((z != 0) && (textMode == SCREEN)) { + String msg = "textMode(SCREEN) cannot have a z coordinate"; + throw new RuntimeException(msg); + } + + if (z != 0) translate(0, 0, z); // slow! + + text(str, x, y); + textZ = z; + + if (z != 0) translate(0, 0, -z); + } + + + /** + * Handles placement of a text line, then calls textLinePlaced + * to actually render at the specific point. + */ + protected void textLineImpl(char buffer[], int start, int stop, + float x, float y) { + if (textAlign == CENTER) { + x -= textWidthImpl(buffer, start, stop) / 2f; + + } else if (textAlign == RIGHT) { + x -= textWidthImpl(buffer, start, stop); + } + + textLinePlacedImpl(buffer, start, stop, x, y); + } + + + protected void textLinePlacedImpl(char buffer[], int start, int stop, + float x, float y) { + for (int index = start; index < stop; index++) { + textCharImpl(buffer[index], x, y); //, 0); //z); + + // this doesn't account for kerning + x += textWidth(buffer[index]); + } + textX = x; + textY = y; + textZ = 0; // this will get set by the caller if non-zero + } + + + /** + * Draw text in a box that is constrained to a particular size. + * The current rectMode() determines what the coordinates mean + * (whether x1/y1/x2/y2 or x/y/w/h). + *

+ * Note that the x,y coords of the start of the box + * will align with the *ascent* of the text, not the baseline, + * as is the case for the other text() functions. + *

+ * Newlines that are \n (Unix newline or linefeed char, ascii 10) + * are honored, and \r (carriage return, Windows and Mac OS) are + * ignored. + */ + public void text(String str, float x1, float y1, float x2, float y2) { + if (textFont == null) { + throw new RuntimeException("use textFont() before text()"); + } + + if (textMode == SCREEN) loadPixels(); + + float hradius, vradius; + switch (rectMode) { + case CORNER: + x2 += x1; y2 += y1; + break; + case RADIUS: + hradius = x2; + vradius = y2; + x2 = x1 + hradius; + y2 = y1 + vradius; + x1 -= hradius; + y1 -= vradius; + break; + case CENTER: + hradius = x2 / 2.0f; + vradius = y2 / 2.0f; + x2 = x1 + hradius; + y2 = y1 + vradius; + x1 -= hradius; + y1 -= vradius; + } + if (x2 < x1) { + float temp = x1; x1 = x2; x2 = temp; + } + if (y2 < y1) { + float temp = y1; y1 = y2; y2 = temp; + } + + float spaceWidth = textWidth(' '); + float runningX = x1; //boxX1; + float currentY = y1; //boxY1; + float boxWidth = x2 - x1; //boxX2 - boxX1; + + // lineX is the position where the text starts, which is adjusted + // to left/center/right based on the current textAlign + float lineX = x1; //boxX1; + if (textAlign == CENTER) { + lineX = lineX + boxWidth/2f; + } else if (textAlign == RIGHT) { + lineX = x2; //boxX2; + } + + // ala illustrator, the text itself must fit inside the box + currentY += textAscent(); //ascent() * textSize; + // if the box is already too small, tell em to f off + if (currentY > y2) return; //boxY2) return; + + int length = str.length(); + if (length > textBuffer.length) { + textBuffer = new char[length + 10]; + } + str.getChars(0, length, textBuffer, 0); + + int wordStart = 0; + int wordStop = 0; + int lineStart = 0; + int index = 0; + while (index < length) { + if ((textBuffer[index] == ' ') || (index == length-1)) { + // boundary of a word + float wordWidth = textWidthImpl(textBuffer, wordStart, index); + + if (runningX + wordWidth > x2) { //boxX2) { + if (runningX == x1) { //boxX1) { + // if this is the first word, and its width is + // greater than the width of the text box, + // then break the word where at the max width, + // and send the rest of the word to the next line. + do { + index--; + if (index == wordStart) { + // not a single char will fit on this line. screw 'em. + //System.out.println("screw you"); + return; + } + wordWidth = textWidthImpl(textBuffer, wordStart, index); + } while (wordWidth > boxWidth); + textLineImpl(textBuffer, lineStart, index, lineX, currentY); + + } else { + // next word is too big, output current line + // and advance to the next line + textLineImpl(textBuffer, lineStart, wordStop, lineX, currentY); + // only increment index if a word wasn't broken inside the + // do/while loop above.. also, this is a while() loop too, + // because multiple spaces don't count for shit when they're + // at the end of a line like this. + + index = wordStop; // back that ass up + while ((index < length) && + (textBuffer[index] == ' ')) { + index++; + } + } + lineStart = index; + wordStart = index; + wordStop = index; + runningX = x1; //boxX1; + currentY += textLeading; + //if (currentY > boxY2) return; // box is now full + if (currentY > y2) return; // box is now full + + } else { + runningX += wordWidth + spaceWidth; + // on to the next word + wordStop = index; + wordStart = index + 1; + } + + } else if (textBuffer[index] == '\n') { + if (lineStart != index) { // if line is not empty + textLineImpl(textBuffer, lineStart, index, lineX, currentY); + } + lineStart = index + 1; + wordStart = lineStart; + runningX = x1; // fix for bug 188 + currentY += textLeading; + //if (currentY > boxY2) return; // box is now full + if (currentY > y2) return; // box is now full + } + index++; + } + if ((lineStart < length) && (lineStart != index)) { + textLineImpl(textBuffer, lineStart, index, lineX, currentY); + } + + if (textMode == SCREEN) updatePixels(); + } + + + public void text(String s, float x1, float y1, float x2, float y2, float z) { + if ((z != 0) && (textMode == SCREEN)) { + String msg = "textMode(SCREEN) cannot have a z coordinate"; + throw new RuntimeException(msg); + } + + if (z != 0) translate(0, 0, z); // slowness, badness + + text(s, x1, y1, x2, y2); + textZ = z; + + if (z != 0) translate(0, 0, -z); // TEMPORARY HACK! SLOW! + } + + + public void text(int num, float x, float y) { + text(String.valueOf(num), x, y); + } + + + public void text(int num, float x, float y, float z) { + text(String.valueOf(num), x, y, z); + } + + + /** + * This does a basic number formatting, to avoid the + * generally ugly appearance of printing floats. + * Users who want more control should use their own nf() cmmand, + * or if they want the long, ugly version of float, + * use String.valueOf() to convert the float to a String first. + */ + public void text(float num, float x, float y) { + text(PApplet.nfs(num, 0, 3), x, y); + } + + + public void text(float num, float x, float y, float z) { + text(PApplet.nfs(num, 0, 3), x, y, z); + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + // what was this for? + //font.getStringBounds(text, g2.getFontRenderContext()).getWidth(); + + + protected void textCharImpl(char ch, float x, float y) { //, float z) { + int index = textFont.index(ch); + if (index == -1) return; + + PImage glyph = textFont.images[index]; + + if (textMode == MODEL) { + float high = (float) textFont.height[index] / textFont.fheight; + float bwidth = (float) textFont.width[index] / textFont.fwidth; + float lextent = (float) textFont.leftExtent[index] / textFont.fwidth; + float textent = (float) textFont.topExtent[index] / textFont.fheight; + + float x1 = x + lextent * textSize; + float y1 = y - textent * textSize; + float x2 = x1 + bwidth * textSize; + float y2 = y1 + high * textSize; + + textCharModelImpl(glyph, + x1, y1, x2, y2, + //x1, y1, z, x2, y2, z, + textFont.width[index], textFont.height[index]); + + } else if (textMode == SCREEN) { + int xx = (int) x + textFont.leftExtent[index];; + int yy = (int) y - textFont.topExtent[index]; + + int w0 = textFont.width[index]; + int h0 = textFont.height[index]; + + textCharScreenImpl(glyph, xx, yy, w0, h0); + } + } + + + protected void textCharModelImpl(PImage glyph, + float x1, float y1, //float z1, + float x2, float y2, //float z2, + int u2, int v2) { + boolean savedTint = tint; + int savedTintColor = tintColor; + float savedTintR = tintR; + float savedTintG = tintG; + float savedTintB = tintB; + float savedTintA = tintA; + boolean savedTintAlpha = tintAlpha; + + tint = true; + tintColor = fillColor; + tintR = fillR; + tintG = fillG; + tintB = fillB; + tintA = fillA; + tintAlpha = fillAlpha; + + imageImpl(glyph, x1, y1, x2, y2, 0, 0, u2, v2); + + tint = savedTint; + tintColor = savedTintColor; + tintR = savedTintR; + tintG = savedTintG; + tintB = savedTintB; + tintA = savedTintA; + tintAlpha = savedTintAlpha; + } + + + // should take image, int x1, int y1, and x2, y2 + + protected void textCharScreenImpl(PImage glyph, + int xx, int yy, //int x2, int y2, + int w0, int h0) { + /* + System.out.println("textimplscreen"); + rectMode(CORNER); + stroke(255); + rect(xx, yy, w0, h0); + */ + + int x0 = 0; + int y0 = 0; + + if ((xx >= width) || (yy >= height) || + (xx + w0 < 0) || (yy + h0 < 0)) return; + + if (xx < 0) { + x0 -= xx; + w0 += xx; + xx = 0; + } + if (yy < 0) { + y0 -= yy; + h0 += yy; + yy = 0; + } + if (xx + w0 > width) { + w0 -= ((xx + w0) - width); + } + if (yy + h0 > height) { + h0 -= ((yy + h0) - height); + } + + int fr = fillRi; + int fg = fillGi; + int fb = fillBi; + int fa = fillAi; + + int pixels1[] = glyph.pixels; //images[glyph].pixels; + + // TODO this can be optimized a bit + for (int row = y0; row < y0 + h0; row++) { + for (int col = x0; col < x0 + w0; col++) { + int a1 = (fa * pixels1[row * textFont.twidth + col]) >> 8; + int a2 = a1 ^ 0xff; + //int p1 = pixels1[row * glyph.width + col]; + int p2 = pixels[(yy + row-y0)*width + (xx+col-x0)]; + + pixels[(yy + row-y0)*width + xx+col-x0] = + (0xff000000 | + (((a1 * fr + a2 * ((p2 >> 16) & 0xff)) & 0xff00) << 8) | + (( a1 * fg + a2 * ((p2 >> 8) & 0xff)) & 0xff00) | + (( a1 * fb + a2 * ( p2 & 0xff)) >> 8)); + } + } + } + + + + ////////////////////////////////////////////////////////////// + + // MATRIX TRANSFORMATIONS + + + public void translate(float tx, float ty) { + m02 += tx*m00 + ty*m01 + m02; + m12 += tx*m10 + ty*m11 + m12; + } + + + public void translate(float tx, float ty, float tz) { + depthErrorXYZ("translate"); + } + + + /** + * Two dimensional rotation. + *

+ * Additional background. + *

+ * Same as rotateZ (this is identical to a 3D rotation along the z-axis) + * but included for clarity. It'd be weird for people drawing 2D graphics + * to be using rotateZ. And they might kick our a-- for the confusion. + */ + public void rotate(float angle) { + float c = (float) Math.cos(angle); + float s = (float) Math.sin(angle); + + applyMatrix(c, -s, 0, s, c, 0); + } + + + public void rotateX(float angle) { + depthError("rotateX"); + } + + public void rotateY(float angle) { + depthError("rotateY"); + } + + + /** + * Rotate around the z axis. The functions rotate() and rotateZ() are + * the same, it's just that it make sense to have rotate() and then rotateX() + * and rotateY() when running in 3D; nor does it make sense to use + * a function called rotateZ() if you're only doing things in 2D. + * so we just decided to have them both be the same. + */ + public void rotateZ(float angle) { + depthError("rotateZ"); + } + + + /** + * Rotate about a vector in space. Same as the glRotatef() function. + */ + public void rotate(float angle, float vx, float vy, float vz) { + throw new RuntimeException("rotate(angle, x, y, z) " + + "can only be used with P3D or OPENGL"); + } + + + public void scale(float s) { + applyMatrix(s, 0, 0, + 0, s, 0); + } + + + public void scale(float sx, float sy) { + applyMatrix(sx, 0, 0, + 0, sy, 0); + } + + + public void scale(float x, float y, float z) { + depthErrorXYZ("scale"); + } + + + + ////////////////////////////////////////////////////////////// + + // TRANSFORMATION MATRIX + + + public void pushMatrix() { + if (matrixStackDepth+1 == MATRIX_STACK_DEPTH) { + throw new RuntimeException("too many calls to pushMatrix()"); + } + float mat[] = matrixStack[matrixStackDepth]; + mat[0] = m00; mat[1] = m01; mat[2] = m02; + mat[3] = m10; mat[4] = m11; mat[5] = m12; + matrixStackDepth++; + } + + + public void popMatrix() { + if (matrixStackDepth == 0) { + throw new RuntimeException("too many calls to popMatrix() " + + "(and not enough to pushMatrix)"); + } + matrixStackDepth--; + float mat[] = matrixStack[matrixStackDepth]; + m00 = mat[0]; m01 = mat[1]; m02 = mat[2]; + m10 = mat[3]; m11 = mat[4]; m12 = mat[5]; + } + + + /** + * Load identity as the transform/model matrix. + * Same as glLoadIdentity(). + */ + public void resetMatrix() { + m00 = 1; m01 = 0; m02 = 0; + m10 = 0; m11 = 1; m12 = 0; + } + + + /** + * Apply a 3x2 affine transformation matrix. + */ + public void applyMatrix(float n00, float n01, float n02, + float n10, float n11, float n12) { + + float r00 = m00*n00 + m01*n10; + float r01 = m00*n01 + m01*n11; + float r02 = m00*n02 + m01*n12 + m02; + + float r10 = m10*n00 + m11*n10; + float r11 = m10*n01 + m11*n11; + float r12 = m10*n02 + m11*n12 + m12; + + m00 = r00; m01 = r01; m02 = r02; + m10 = r10; m11 = r11; m12 = r12; + } + + + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + throw new RuntimeException("applyMatrix() with a 4x4 matrix " + + "can only be used with OPENGL or P3D"); + } + + + /** + * Loads the current matrix into m00, m01 etc (or modelview and + * projection when using 3D) so that the values can be read. + *

+ * Note that there is no "updateMatrix" because that gets too + * complicated (unnecessary) when considering the 3D matrices. + */ + public void loadMatrix() { + // no-op on base PGraphics because they're used directly + } + + + /** + * Print the current model (or "transformation") matrix. + */ + public void printMatrix() { + loadMatrix(); // just to make sure + + float big = Math.abs(m00); + if (Math.abs(m01) > big) big = Math.abs(m01); + if (Math.abs(m02) > big) big = Math.abs(m02); + if (Math.abs(m10) > big) big = Math.abs(m10); + if (Math.abs(m11) > big) big = Math.abs(m11); + if (Math.abs(m12) > big) big = Math.abs(m12); + + // avoid infinite loop + if (Float.isNaN(big) || Float.isInfinite(big)) { + big = 1000000; // set to something arbitrary + } + + int d = 1; + int bigi = (int) big; + while ((bigi /= 10) != 0) d++; // cheap log() + + System.out.println(PApplet.nfs(m00, d, 4) + " " + + PApplet.nfs(m01, d, 4) + " " + + PApplet.nfs(m02, d, 4)); + + System.out.println(PApplet.nfs(m10, d, 4) + " " + + PApplet.nfs(m11, d, 4) + " " + + PApplet.nfs(m12, d, 4)); + + System.out.println(); + } + + + + ////////////////////////////////////////////////////////////// + + // CAMERA (none are supported in 2D) + + + public void beginCamera() { + depthError("beginCamera"); + } + + public void endCamera() { + depthError("endCamera"); + } + + public void camera() { + depthError("camera"); + } + + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + depthError("camera"); + } + + public void printCamera() { + depthError("printCamera"); + } + + + + ////////////////////////////////////////////////////////////// + + // PROJECTION (none are supported in 2D) + + + public void ortho() { + depthError("ortho"); + } + + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + depthError("ortho"); + } + + public void perspective() { + depthError("perspective"); + } + + public void perspective(float fovy, float aspect, float zNear, float zFar) { + depthError("perspective"); + } + + public void frustum(float left, float right, float bottom, + float top, float znear, float zfar) { + depthError("frustum"); + } + + public void printProjection() { + depthError("printCamera"); + } + + + + ////////////////////////////////////////////////////////////// + + // SCREEN TRANSFORMS + + + /** + * Given an x and y coordinate, returns the x position of where + * that point would be placed on screen, once affected by translate(), + * scale(), or any other transformations. + */ + public float screenX(float x, float y) { + return m00*x + m01*y + m02; + } + + + /** + * Given an x and y coordinate, returns the y position of where + * that point would be placed on screen, once affected by translate(), + * scale(), or any other transformations. + */ + public float screenY(float x, float y) { + return m10*x + m11*y + m12; + } + + + /** + * Maps a three dimensional point to its placement on-screen. + *

+ * Given an (x, y, z) coordinate, returns the x position of where + * that point would be placed on screen, once affected by translate(), + * scale(), or any other transformations. + */ + public float screenX(float x, float y, float z) { + depthErrorXYZ("screenX"); + return 0; + } + + + /** + * Maps a three dimensional point to its placement on-screen. + *

+ * Given an (x, y, z) coordinate, returns the y position of where + * that point would be placed on screen, once affected by translate(), + * scale(), or any other transformations. + */ + public float screenY(float x, float y, float z) { + depthErrorXYZ("screenY"); + return 0; + } + + + /** + * Maps a three dimensional point to its placement on-screen. + *

+ * Given an (x, y, z) coordinate, returns its z value. + * This value can be used to determine if an (x, y, z) coordinate + * is in front or in back of another (x, y, z) coordinate. + * The units are based on how the zbuffer is set up, and don't + * relate to anything "real". They're only useful for in + * comparison to another value obtained from screenZ(), + * or directly out of the zbuffer[]. + */ + public float screenZ(float x, float y, float z) { + depthErrorXYZ("screenZ"); + return 0; + } + + + /** + * Returns the model space x value for an x, y, z coordinate. + *

+ * This will give you a coordinate after it has been transformed + * by translate(), rotate(), and camera(), but not yet transformed + * by the projection matrix. For instance, his can be useful for + * figuring out how points in 3D space relate to the edge + * coordinates of a shape. + */ + public float modelX(float x, float y, float z) { + depthError("modelX"); + return 0; + } + + + /** + * Returns the model space y value for an x, y, z coordinate. + */ + public float modelY(float x, float y, float z) { + depthError("modelY"); + return 0; + } + + + /** + * Returns the model space z value for an x, y, z coordinate. + */ + public float modelZ(float x, float y, float z) { + depthError("modelZ"); + return 0; + } + + + + ////////////////////////////////////////////////////////////// + + // COLOR + + + public void colorMode(int mode) { + colorMode(mode, colorModeX, colorModeY, colorModeZ, colorModeA); + } + + + public void colorMode(int mode, float max) { + colorMode(mode, max, max, max, max); + } + + + /** + * Set the colorMode and the maximum values for (r, g, b) + * or (h, s, b). + *

+ * Note that this doesn't set the maximum for the alpha value, + * which might be confusing if for instance you switched to + *

colorMode(HSB, 360, 100, 100);
+ * because the alpha values were still between 0 and 255. + */ + public void colorMode(int mode, + float maxX, float maxY, float maxZ) { + colorMode(mode, maxX, maxY, maxZ, colorModeA); + } + + + public void colorMode(int mode, + float maxX, float maxY, float maxZ, float maxA) { + colorMode = mode; + + colorModeX = maxX; // still needs to be set for hsb + colorModeY = maxY; + colorModeZ = maxZ; + colorModeA = maxA; + + // if color max values are all 1, then no need to scale + colorScale = ((maxA != 1) || (maxX != maxY) || + (maxY != maxZ) || (maxZ != maxA)); + + // if color is rgb/0..255 this will make it easier for the + // red() green() etc functions + colorRgb255 = (colorMode == RGB) && + (colorModeA == 255) && (colorModeX == 255) && + (colorModeY == 255) && (colorModeZ == 255); + } + + + ////////////////////////////////////////////////////////////// + + + protected void colorCalc(float gray) { + colorCalc(gray, colorModeA); + } + + + protected void colorCalc(float gray, float alpha) { + if (gray > colorModeX) gray = colorModeX; + if (alpha > colorModeA) alpha = colorModeA; + + if (gray < 0) gray = 0; + if (alpha < 0) alpha = 0; + + calcR = colorScale ? (gray / colorModeX) : gray; + calcG = calcR; + calcB = calcR; + calcA = colorScale ? (alpha / colorModeA) : alpha; + + calcRi = (int)(calcR*255); calcGi = (int)(calcG*255); + calcBi = (int)(calcB*255); calcAi = (int)(calcA*255); + calcColor = (calcAi << 24) | (calcRi << 16) | (calcGi << 8) | calcBi; + calcAlpha = (calcAi != 255); + } + + + protected void colorCalc(float x, float y, float z) { + colorCalc(x, y, z, colorModeA); + } + + + protected void colorCalc(float x, float y, float z, float a) { + if (x > colorModeX) x = colorModeX; + if (y > colorModeY) y = colorModeY; + if (z > colorModeZ) z = colorModeZ; + if (a > colorModeA) a = colorModeA; + + if (x < 0) x = 0; + if (y < 0) y = 0; + if (z < 0) z = 0; + if (a < 0) a = 0; + + switch (colorMode) { + case RGB: + if (colorScale) { + calcR = x / colorModeX; + calcG = y / colorModeY; + calcB = z / colorModeZ; + calcA = a / colorModeA; + } else { + calcR = x; calcG = y; calcB = z; calcA = a; + } + break; + + case HSB: + x /= colorModeX; // h + y /= colorModeY; // s + z /= colorModeZ; // b + + calcA = colorScale ? (a/colorModeA) : a; + + if (y == 0) { // saturation == 0 + calcR = calcG = calcB = z; + + } else { + float which = (x - (int)x) * 6.0f; + float f = which - (int)which; + float p = z * (1.0f - y); + float q = z * (1.0f - y * f); + float t = z * (1.0f - (y * (1.0f - f))); + + switch ((int)which) { + case 0: calcR = z; calcG = t; calcB = p; break; + case 1: calcR = q; calcG = z; calcB = p; break; + case 2: calcR = p; calcG = z; calcB = t; break; + case 3: calcR = p; calcG = q; calcB = z; break; + case 4: calcR = t; calcG = p; calcB = z; break; + case 5: calcR = z; calcG = p; calcB = q; break; + } + } + break; + } + calcRi = (int)(255*calcR); calcGi = (int)(255*calcG); + calcBi = (int)(255*calcB); calcAi = (int)(255*calcA); + calcColor = (calcAi << 24) | (calcRi << 16) | (calcGi << 8) | calcBi; + calcAlpha = (calcAi != 255); + } + + + /** + * Unpacks AARRGGBB color for direct use with colorCalc. + *

+ * Handled here with its own function since this is indepenent + * of the color mode. + *

+ * Strangely the old version of this code ignored the alpha + * value. not sure if that was a bug or what. + *

+ * Note, no need for a bounds check since it's a 32 bit number. + */ + protected void colorCalcARGB(int argb, float alpha) { + if (alpha == colorModeA) { + calcAi = (argb >> 24) & 0xff; + calcColor = argb; + } else { + calcAi = (int) (((argb >> 24) & 0xff) * (alpha / colorModeA)); + calcColor = (calcAi << 24) | (argb & 0xFFFFFF); + } + calcRi = (argb >> 16) & 0xff; + calcGi = (argb >> 8) & 0xff; + calcBi = argb & 0xff; + calcA = (float)calcAi / 255.0f; + calcR = (float)calcRi / 255.0f; + calcG = (float)calcGi / 255.0f; + calcB = (float)calcBi / 255.0f; + calcAlpha = (calcAi != 255); + + } + + + ////////////////////////////////////////////////////////////// + + + public void strokeWeight(float weight) { + strokeWeight = weight; + } + + + public void strokeJoin(int join) { + strokeJoin = join; + } + + + public void strokeCap(int cap) { + strokeCap = cap; + } + + + public void noStroke() { + stroke = false; + } + + + /** + * Set the tint to either a grayscale or ARGB value. + * See notes attached to the fill() function. + */ + public void stroke(int rgb) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above + stroke((float) rgb); + + } else { + colorCalcARGB(rgb, colorModeA); + strokeFromCalc(); + } + } + + + public void stroke(int rgb, float alpha) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { + stroke((float) rgb, alpha); + + } else { + colorCalcARGB(rgb, alpha); + strokeFromCalc(); + } + } + + + public void stroke(float gray) { + colorCalc(gray); + strokeFromCalc(); + } + + + public void stroke(float gray, float alpha) { + colorCalc(gray, alpha); + strokeFromCalc(); + } + + + public void stroke(float x, float y, float z) { + colorCalc(x, y, z); + strokeFromCalc(); + } + + + public void stroke(float x, float y, float z, float a) { + colorCalc(x, y, z, a); + strokeFromCalc(); + } + + + protected void strokeFromCalc() { + stroke = true; + //strokeChanged = true; + strokeR = calcR; + strokeG = calcG; + strokeB = calcB; + strokeA = calcA; + strokeRi = calcRi; + strokeGi = calcGi; + strokeBi = calcBi; + strokeAi = calcAi; + strokeColor = calcColor; + strokeAlpha = calcAlpha; + } + + + ////////////////////////////////////////////////////////////// + + + public void noTint() { + tint = false; + } + + + /** + * Set the tint to either a grayscale or ARGB value. See notes + * attached to the fill() function. + */ + public void tint(int rgb) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { + tint((float) rgb); + + } else { + colorCalcARGB(rgb, colorModeA); + tintFromCalc(); + } + } + + public void tint(int rgb, float alpha) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { + tint((float) rgb, alpha); + + } else { + colorCalcARGB(rgb, alpha); + tintFromCalc(); + } + } + + public void tint(float gray) { + colorCalc(gray); + tintFromCalc(); + } + + + public void tint(float gray, float alpha) { + colorCalc(gray, alpha); + tintFromCalc(); + } + + + public void tint(float x, float y, float z) { + colorCalc(x, y, z); + tintFromCalc(); + } + + + public void tint(float x, float y, float z, float a) { + colorCalc(x, y, z, a); + tintFromCalc(); + } + + + protected void tintFromCalc() { + tint = true; + tintR = calcR; + tintG = calcG; + tintB = calcB; + tintA = calcA; + tintRi = calcRi; + tintGi = calcGi; + tintBi = calcBi; + tintAi = calcAi; + tintColor = calcColor; + tintAlpha = calcAlpha; + } + + + ////////////////////////////////////////////////////////////// + + + public void noFill() { + fill = false; + } + + + /** + * Set the fill to either a grayscale value or an ARGB int. + *

+ * The problem with this code is that it has to detect between + * these two situations automatically. This is done by checking + * to see if the high bits (the alpha for 0xAA000000) is set, + * and if not, whether the color value that follows is less than + * colorModeX (the first param passed to colorMode). + *

+ * This auto-detect would break in the following situation: + *

size(256, 256);
+   * for (int i = 0; i < 256; i++) {
+   *   color c = color(0, 0, 0, i);
+   *   stroke(c);
+   *   line(i, 0, i, 256);
+   * }
+ * ...on the first time through the loop, where (i == 0), + * since the color itself is zero (black) then it would appear + * indistinguishable from someone having written fill(0). + */ + public void fill(int rgb) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above + fill((float) rgb); + + } else { + colorCalcARGB(rgb, colorModeA); + fillFromCalc(); + } + } + + + public void fill(int rgb, float alpha) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above + fill((float) rgb, alpha); + + } else { + colorCalcARGB(rgb, alpha); + fillFromCalc(); + } + } + + + public void fill(float gray) { + colorCalc(gray); + fillFromCalc(); + } + + + public void fill(float gray, float alpha) { + colorCalc(gray, alpha); + fillFromCalc(); + } + + + public void fill(float x, float y, float z) { + colorCalc(x, y, z); + fillFromCalc(); + } + + + public void fill(float x, float y, float z, float a) { + colorCalc(x, y, z, a); + fillFromCalc(); + } + + + protected void fillFromCalc() { + fill = true; + fillR = calcR; + fillG = calcG; + fillB = calcB; + fillA = calcA; + fillRi = calcRi; + fillGi = calcGi; + fillBi = calcBi; + fillAi = calcAi; + fillColor = calcColor; + fillAlpha = calcAlpha; + } + + + ////////////////////////////////////////////////////////////// + + + public void ambient(int rgb) { + depthError("ambient"); + } + + public void ambient(float gray) { + depthError("ambient"); + } + + public void ambient(float x, float y, float z) { + depthError("ambient"); + } + + + ////////////////////////////////////////////////////////////// + + + public void specular(int rgb) { + depthError("specular"); + } + + public void specular(float gray) { + depthError("specular"); + } + + public void specular(float gray, float alpha) { + depthError("specular"); + } + + public void specular(float x, float y, float z) { + depthError("specular"); + } + + public void specular(float x, float y, float z, float a) { + depthError("specular"); + } + + public void shininess(float shine) { + depthError("shininess"); + } + + + ////////////////////////////////////////////////////////////// + + + public void emissive(int rgb) { + depthError("emissive"); + } + + public void emissive(float gray) { + depthError("emissive"); + } + + public void emissive(float x, float y, float z ) { + depthError("emissive"); + } + + + + ////////////////////////////////////////////////////////////// + + // LIGHTS + + + public void lights() { + depthError("lights"); + } + + public void ambientLight(float red, float green, float blue) { + depthError("ambientLight"); + } + + public void ambientLight(float red, float green, float blue, + float x, float y, float z) { + depthError("ambientLight"); + } + + public void directionalLight(float red, float green, float blue, + float nx, float ny, float nz) { + depthError("directionalLight"); + } + + public void pointLight(float red, float green, float blue, + float x, float y, float z) { + depthError("pointLight"); + } + + public void spotLight(float red, float green, float blue, + float x, float y, float z, + float nx, float ny, float nz, + float angle, float concentration) { + depthError("spotLight"); + } + + public void lightFalloff(float constant, float linear, float quadratic) { + depthError("lightFalloff"); + } + + public void lightSpecular(float x, float y, float z) { + depthError("lightSpecular"); + } + + + + ////////////////////////////////////////////////////////////// + + + /** + * Set the background to a gray or ARGB color. + *

+ * Note that background() should be called before any + * transformations occur, because some implementations may + * require the current transformation matrix to be identity + * before drawing. + */ + public void background(int rgb) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { + background((float) rgb); + + } else { + colorCalcARGB(rgb, colorModeA); + backgroundFromCalc(); + } + clear(); + } + + + /** + * See notes about alpha in background(x, y, z, a). + */ + public void background(int rgb, float alpha) { + if (mainDrawingSurface) { + background(rgb); // don't allow people to set alpha + + } else { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above + background((float) rgb, alpha); + + } else { + colorCalcARGB(rgb, alpha); + backgroundFromCalc(); + clear(); + } + } + } + + + /** + * Set the background to a grayscale value, based on the + * current colorMode. + */ + public void background(float gray) { + colorCalc(gray); + backgroundFromCalc(); + clear(); + } + + + /** + * See notes about alpha in background(x, y, z, a). + */ + public void background(float gray, float alpha) { + if (mainDrawingSurface) { + background(gray); // don't allow people to set alpha + + } else { + colorCalc(gray, alpha); + backgroundFromCalc(); + clear(); + } + } + + + /** + * Set the background to an r, g, b or h, s, b value, + * based on the current colorMode. + */ + public void background(float x, float y, float z) { + colorCalc(x, y, z); + backgroundFromCalc(); + clear(); + } + + + /** + * Clear the background with a color that includes an alpha value. + * This should only be used with objects created by createGraphics(), + * setting the main drawing surface transparent may cause problems. + * It might be tempting to use this function to partially clear the + * screen on each frame, however that's not how this function works. + * When calling background(), the pixels will be replaced with pixels + * that have that level of transparency. To do a semi-transparent + * overlay, use fill() with alpha and draw a rectangle. + */ + public void background(float x, float y, float z, float a) { + if (mainDrawingSurface) { + background(x, y, z); // don't allow people to set alpha + + } else { + colorCalc(x, y, z, a); + backgroundFromCalc(); + clear(); + } + } + + + protected void backgroundFromCalc() { + backgroundR = calcR; + backgroundG = calcG; + backgroundB = calcB; + backgroundA = calcA; + backgroundRi = calcRi; + backgroundGi = calcGi; + backgroundBi = calcBi; + backgroundAi = calcAi; + backgroundAlpha = calcAlpha; + backgroundColor = calcColor; + } + + + /** + * Takes an RGB or ARGB image and sets it as the background. + *

+ * Note that even if the image is set as RGB, the high 8 bits of + * each pixel should be set opaque (0xFF000000), because the image data + * will be copied directly to the screen, and non-opaque background + * images may have strange behavior. Using image.filter(OPAQUE) + * will handle this easily. + *

+ * When using 3D, this will also clear out the zbuffer and + * stencil buffer if they exist. + */ + public void background(PImage image) { + if ((image.width != width) || (image.height != height)) { + throw new RuntimeException("background image must be " + + "the same size as your application"); + } + if ((image.format != RGB) && (image.format != ARGB)) { + throw new RuntimeException("background images should be RGB or ARGB"); + } + + // zero this out since it's an image + backgroundColor = 0; + + // blit image to the screen + System.arraycopy(image.pixels, 0, pixels, 0, pixels.length); + } + + + /** + * Clear the pixel buffer. + */ + abstract protected void clear(); + + + + ////////////////////////////////////////////////////////////// + + // MESSAGES / ERRORS / LOGGING + + + protected void depthError(String method) { + throw new RuntimeException(method + "() can only be used " + + "with P3D or OPENGL."); + } + + + protected void depthErrorXYZ(String method) { + throw new RuntimeException(method + "(x, y, z) can only be used with " + + "OPENGL or P3D, use " + + method + "(x, y) instead."); + } + + + protected void unavailableError(String methodStr) { + throw new RuntimeException(methodStr + + " is not available with this renderer"); + } + + + + ////////////////////////////////////////////////////////////// + + // COLOR MANIPULATION + + // these functions are really slow, but easy to use + // if folks are advanced enough to want something faster, + // they can write it themselves (not difficult) + + + public final int color(int gray) { // ignore + if (((gray & 0xff000000) == 0) && (gray <= colorModeX)) { + if (colorRgb255) { + // bounds checking to make sure the numbers aren't to high or low + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + return 0xff000000 | (gray << 16) | (gray << 8) | gray; + } else { + colorCalc(gray); + } + } else { + colorCalcARGB(gray, colorModeA); + } + return calcColor; + } + + public final int color(float gray) { // ignore + colorCalc(gray); + return calcColor; + } + + + /** + * @param gray can be packed ARGB or a gray in this case + */ + public final int color(int gray, int alpha) { // ignore + if (colorRgb255) { + // bounds checking to make sure the numbers aren't to high or low + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + if (alpha > 255) alpha = 255; else if (alpha < 0) alpha = 0; + + return ((alpha & 0xff) << 24) | (gray << 16) | (gray << 8) | gray; + } + colorCalc(gray, alpha); + return calcColor; + } + + /** + * @param rgb can be packed ARGB or a gray in this case + */ + public final int color(int rgb, float alpha) { // ignore + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { + colorCalc(rgb, alpha); + } else { + colorCalcARGB(rgb, alpha); + } + return calcColor; + } + + public final int color(float gray, float alpha) { // ignore + colorCalc(gray, alpha); + return calcColor; + } + + + public final int color(int x, int y, int z) { // ignore + if (colorRgb255) { + // bounds checking to make sure the numbers aren't to high or low + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return 0xff000000 | (x << 16) | (y << 8) | z; + } + colorCalc(x, y, z); + return calcColor; + } + + public final int color(float x, float y, float z) { // ignore + colorCalc(x, y, z); + return calcColor; + } + + + public final int color(int x, int y, int z, int a) { // ignore + if (colorRgb255) { + // bounds checking to make sure the numbers aren't to high or low + if (a > 255) a = 255; else if (a < 0) a = 0; + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return (a << 24) | (x << 16) | (y << 8) | z; + } + colorCalc(x, y, z, a); + return calcColor; + } + + public final int color(float x, float y, float z, float a) { // ignore + colorCalc(x, y, z, a); + return calcColor; + } + + + public final float alpha(int what) { + float c = (what >> 24) & 0xff; + if (colorModeA == 255) return c; + return (c / 255.0f) * colorModeA; + } + + public final float red(int what) { + float c = (what >> 16) & 0xff; + if (colorRgb255) return c; + return (c / 255.0f) * colorModeX; + } + + public final float green(int what) { + float c = (what >> 8) & 0xff; + if (colorRgb255) return c; + return (c / 255.0f) * colorModeY; + } + + public final float blue(int what) { + float c = (what) & 0xff; + if (colorRgb255) return c; + return (c / 255.0f) * colorModeZ; + } + + + public final float hue(int what) { + if (what != cacheHsbKey) { + Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff, + what & 0xff, cacheHsbValue); + cacheHsbKey = what; + } + return cacheHsbValue[0] * colorModeX; + } + + public final float saturation(int what) { + if (what != cacheHsbKey) { + Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff, + what & 0xff, cacheHsbValue); + cacheHsbKey = what; + } + return cacheHsbValue[1] * colorModeY; + } + + public final float brightness(int what) { + if (what != cacheHsbKey) { + Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff, + what & 0xff, cacheHsbValue); + cacheHsbKey = what; + } + return cacheHsbValue[2] * colorModeZ; + } + + + public int lerpColor(int c1, int c2, float amt) { + return lerpColor(c1, c2, amt, colorMode); + } + + static float[] lerpColorHSB1; + static float[] lerpColorHSB2; + + static public int lerpColor(int c1, int c2, float amt, int mode) { + if (mode == RGB) { + float a1 = ((c1 >> 24) & 0xff); + float r1 = (c1 >> 16) & 0xff; + float g1 = (c1 >> 8) & 0xff; + float b1 = c1 & 0xff; + float a2 = (c2 >> 24) & 0xff; + float r2 = (c2 >> 16) & 0xff; + float g2 = (c2 >> 8) & 0xff; + float b2 = c2 & 0xff; + + return (((int) (a1 + (a2-a1)*amt) << 24) | + ((int) (r1 + (r2-r1)*amt) << 16) | + ((int) (g1 + (g2-g1)*amt) << 8) | + ((int) (b1 + (b2-b1)*amt))); + + } else if (mode == HSB) { + if (lerpColorHSB1 == null) { + lerpColorHSB1 = new float[3]; + lerpColorHSB2 = new float[3]; + } + + float a1 = (c1 >> 24) & 0xff; + float a2 = (c2 >> 24) & 0xff; + int alfa = ((int) (a1 + (a2-a1)*amt)) << 24; + + Color.RGBtoHSB((c1 >> 16) & 0xff, (c1 >> 8) & 0xff, c1 & 0xff, + lerpColorHSB1); + Color.RGBtoHSB((c2 >> 16) & 0xff, (c2 >> 8) & 0xff, c2 & 0xff, + lerpColorHSB2); + + // roll around when 0.9 to 0.1 + // more than 0.5 away means that it should roll in the other direction + float h1 = lerpColorHSB1[0]; + float h2 = lerpColorHSB2[0]; + if (Math.abs(h1 - h2) > 0.5f) { + if (h1 > h2) { + // i.e. h1 is 0.7, h2 is 0.1 + h2 += 1; + } else { + // i.e. h1 is 0.1, h2 is 0.7 + h1 += 1; + } + } + float ho = (PApplet.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt)) % 1.0f; + float so = PApplet.lerp(lerpColorHSB1[1], lerpColorHSB2[1], amt); + float bo = PApplet.lerp(lerpColorHSB1[2], lerpColorHSB2[2], amt); + + return alfa | (Color.HSBtoRGB(ho, so, bo) & 0xFFFFFF); + } + return 0; + } + + + ////////////////////////////////////////////////////////////// + + // MATH + + + static final float sqrt(float a) { + return (float)Math.sqrt(a); + } + + + + ////////////////////////////////////////////////////////////// + + // PATH + + /* + class Path { + + public void moveTo(float x, float y) { // ignore + } + + public void lineTo(float x, float y) { // ignore + } + + public void curveTo(float x1, float y1, // ignore + float x2, float y2, + float x3, float y3) { + } + + public void closePath() { // ignore + } + } + */ + + + ////////////////////////////////////////////////////////////// + + + /** + * Use with caution on PGraphics. This should not be used with + * the base PGraphics that's tied to a PApplet, but it can be used + * with user-created PGraphics objects that are drawn to the screen. + */ + public void mask(int alpha[]) { // ignore + super.mask(alpha); + } + + + /** + * Use with caution on PGraphics. This should not be used with + * the base PGraphics that's tied to a PApplet, but it can be used + * with user-created PGraphics objects that are drawn to the screen. + */ + public void mask(PImage alpha) { // ignore + super.mask(alpha); + } + + + ////////////////////////////////////////////////////////////// + + + public void beginRaw(PGraphics rawGraphics) { + this.raw = rawGraphics; + rawGraphics.beginDraw(); + } + + + public void endRaw() { + if (raw != null) { + // for 3D, need to flush any geometry that's been stored for sorting + raw.flush(); // this should be called by endDraw() instead + + // just like beginDraw, this will have to be called because + // endDraw() will be happening outside of draw() + raw.endDraw(); + raw.dispose(); + raw = null; + } + } + + + /** + * Handle any takedown for this graphics context. + *

+ * This is called when a sketch is shut down and this renderer was + * specified using the size() command, or inside endRecord() and + * endRaw(), in order to shut things off. + */ + public void dispose() { // ignore + } + + + /** + * Return true if this renderer should be drawn to the screen. + * Overridden for subclasses like PDF so that an enormous window + * doesn't open up. + * showFrame, displayable, isVisible, visible, shouldDisplay, + * what to call this? + */ + public boolean displayable() { + return true; + } +} diff --git a/core/PGraphics2D.java b/core/PGraphics2D.java new file mode 100644 index 000000000..3899b52e9 --- /dev/null +++ b/core/PGraphics2D.java @@ -0,0 +1,1672 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2006 Ben Fry and Casey Reas + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.Toolkit; +import java.awt.image.DirectColorModel; +import java.awt.image.MemoryImageSource; + + +/** + * Subclass of PGraphics that handles fast 2D rendering, + * more commonly referred to as P2D. This class uses no Java2D + * and will run with Java 1.1. + */ +public class PGraphics2D extends PGraphics { + + PPolygon polygon; // general polygon to use for shape + PPolygon fpolygon; // used to fill polys for tri or quad strips + PPolygon spolygon; // stroke/line polygon + float svertices[][]; // temp vertices used for stroking end of poly + + // polygon that handles tesselation + private PPolygon tpolygon; + private int TPOLYGON_MAX_VERTICES = 512; + private int tpolygon_vertex_order[]; // = new int[MAX_VERTICES]; + + PLine line; + + //boolean untransformed; + boolean strokeChanged = true; + boolean fillChanged = true; + + static final int CVERTEX_ALLOC = 128; + float cvertex[][] = new float[CVERTEX_ALLOC][VERTEX_FIELD_COUNT]; + int cvertexIndex; + + + ////////////////////////////////////////////////////////////// + + + //protected PGraphics2D() { } + + + /* + public PGraphics2D(int iwidth, int iheight) { + this(iwidth, iheight, null); + } + */ + + + public PGraphics2D(int iwidth, int iheight, PApplet applet) { + super(iwidth, iheight, applet); + /* + if (applet != null) { + this.parent = applet; + applet.addListeners(); + } + resize(iwidth, iheight); + */ + } + + + //resize handled by superclass + + + //requestDisplay handled by superclass + + + protected void allocate() { + pixelCount = width * height; + pixels = new int[pixelCount]; + + // because of a java 1.1 bug, pixels must be registered as + // opaque before their first run, the memimgsrc will flicker + // and run very slowly. + backgroundColor |= 0xff000000; // just for good measure + for (int i = 0; i < pixelCount; i++) pixels[i] = backgroundColor; + //for (int i = 0; i < pixelCount; i++) pixels[i] = 0xffffffff; + + //if (parent != null) { + if (mainDrawingSurface) { + cm = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff);; + mis = new MemoryImageSource(width, height, pixels, 0, width); + mis.setFullBufferUpdates(true); + mis.setAnimated(true); + image = Toolkit.getDefaultToolkit().createImage(mis); + } + + // can't un-set this because this may be only a resize (Bug #463) + //defaultsInited = false; + } + + + ////////////////////////////////////////////////////////////// + + + public void beginDraw() { + insideResizeWait(); + insideDraw = true; + + // need to call defaults(), but can only be done when it's ok + // to draw (i.e. for opengl, no drawing can be done outside + // beginDraw/endDraw). + if (!defaultsInited) { + defaults(); + + polygon = new PPolygon(this); + fpolygon = new PPolygon(this); + spolygon = new PPolygon(this); + spolygon.vertexCount = 4; + svertices = new float[2][]; + } + + resetMatrix(); // reset model matrix + + // reset vertices + vertexCount = 0; + } + + + public void endDraw() { + // moving this back here (post-68) because of macosx thread problem + if (mis != null) { + mis.newPixels(pixels, cm, 0, width); + } + // mark pixels as having been updated, so that they'll work properly + // when this PGraphics is drawn using image(). + updatePixels(); + + insideDraw = false; + } + + + ////////////////////////////////////////////////////////////// + + + public void beginShape(int kind) { + shape = kind; + vertexCount = 0; + splineVertexCount = 0; + + polygon.reset(0); + fpolygon.reset(4); + spolygon.reset(4); + + polygon.interpUV = false; + } + + + // PGraphics will throw a depthError + //public void normal(float nx, float ny, float nz) + + // PGraphics will handle setting these + //public void textureMode(int mode) + //public void texture(PImage image) + //protected void textureVertex(float u, float v) + + + public void vertex(float x, float y) { + float vertex[] = polygon.nextVertex(); + cvertexIndex = 0; // reset curves to start + + vertex[MX] = x; + vertex[MY] = y; + + if (fill) { + vertex[R] = fillR; + vertex[G] = fillG; + vertex[B] = fillB; + vertex[A] = fillA; + } + + if (stroke) { + vertex[SR] = strokeR; + vertex[SG] = strokeG; + vertex[SB] = strokeB; + vertex[SA] = strokeA; + vertex[SW] = strokeWeight; + } + + // this complicated if construct may defeat the purpose + if (textureImage != null) { + vertex[U] = textureU; + vertex[V] = textureV; + } + } + + + public void vertex(float x, float y, float u, float v) { + textureVertex(u, v); + vertex(x, y); + } + + + public void vertex(float x, float y, float z) { + depthErrorXYZ("vertex"); + } + + + public void vertex(float x, float y, float z, float u, float v) { + depthErrorXYZ("vertex"); + } + + + public void endShape(int mode) { + // clear the 'shape drawing' flag in case of early exit + //shape = 0; + // hm can't do anymore.. + + int polyVertexCount = polygon.vertexCount; + float polyVertices[][] = polygon.vertices; + + if (untransformed()) { + for (int i = 0; i < polyVertexCount; i++) { + polyVertices[i][X] = polyVertices[i][MX]; + polyVertices[i][Y] = polyVertices[i][MY]; + } + } else { + for (int i = 0; i < polyVertexCount; i++) { + polyVertices[i][X] = m00*polyVertices[i][MX] + m01*polyVertices[i][MY] + m03; + polyVertices[i][Y] = m10*polyVertices[i][MX] + m11*polyVertices[i][MY] + m13; + } + } + + // ------------------------------------------------------------------ + // TEXTURES + + if (polygon.interpUV) { + fpolygon.texture(textureImage); //polygon.timage); + } + + + // ------------------------------------------------------------------ + // COLORS + // calculate RGB for each vertex + + spolygon.interpARGB = strokeChanged; //false; + fpolygon.interpARGB = fillChanged; //false; + + // all the values for r, g, b have been set with calls to vertex() + // (no need to re-calculate anything here) + + + // ------------------------------------------------------------------ + // RENDER SHAPES + + int increment; + + switch (shape) { + case POINTS: + if (untransformed() && (strokeWeight == 1)) { + if (!strokeChanged) { + for (int i = 0; i < polyVertexCount; i++) { + thin_point((int) polyVertices[i][X], (int) polyVertices[i][Y], + 0, strokeColor); + } + } else { + for (int i = 0; i < polyVertexCount; i++) { + thin_point((int) polyVertices[i][X], (int) polyVertices[i][Y], + 0, float_color(polyVertices[i][SR], + polyVertices[i][SG], + polyVertices[i][SB])); + } + //strokei = strokeiSaved; + } + } else { + float f[] = polyVertices[0]; + + for (int i = 0; i < polyVertexCount; i++) { + float v[] = polyVertices[i]; + + // if this is the first time (i == 0) + // or if lighting is enabled + // or the stroke color has changed inside beginShape/endShape + // then re-calculate the color at this vertex + if ((i == 0) || strokeChanged) { + // push calculated color into 'f' (this way, f is always valid) + calc_lighting(v[SR], v[SG], v[SB], + v[X], v[Y], v[Z], + v[NX], v[NY], v[NZ], f, R); + } + // uses [SA], since stroke alpha isn't moved into [A] the + // way that [SR] goes to [R] etc on the calc_lighting call + // (there's no sense in copying it to [A], except consistency + // in the code.. but why the extra slowness?) + thick_point(v[X], v[Y], v[Z], f[R], f[G], f[B], f[SA]); + } + } + break; + + case LINES: + //case LINE_STRIP: + //case LINE_LOOP: + if (!stroke) return; + + // if it's a line loop, copy the vertex data to the last element + //if (shape == LINE_LOOP) { + if (mode == CLOSE) { + float v0[] = polygon.vertices[0]; + float v1[] = polygon.nextVertex(); + polyVertexCount++; // since it had already been read above + + v1[X] = v0[X]; v1[Y] = v0[Y]; v1[Z] = v0[Z]; + v1[SR] = v0[SR]; v1[SG] = v0[SG]; v1[SB] = v0[SB]; + } + + // increment by two for individual lines + increment = (shape == LINES) ? 2 : 1; + draw_lines(polyVertices, polyVertexCount-1, 1, increment, 0); + break; + + case TRIANGLES: + case TRIANGLE_STRIP: + increment = (shape == TRIANGLES) ? 3 : 1; + // do fill and stroke separately because otherwise + // the lines will be stroked more than necessary + if (fill) { + fpolygon.vertexCount = 3; + for (int i = 0; i < polyVertexCount-2; i += increment) { + for (int j = 0; j < 3; j++) { + fpolygon.vertices[j][R] = polyVertices[i+j][R]; + fpolygon.vertices[j][G] = polyVertices[i+j][G]; + fpolygon.vertices[j][B] = polyVertices[i+j][B]; + fpolygon.vertices[j][A] = polyVertices[i+j][A]; + + fpolygon.vertices[j][X] = polyVertices[i+j][X]; + fpolygon.vertices[j][Y] = polyVertices[i+j][Y]; + fpolygon.vertices[j][Z] = polyVertices[i+j][Z]; + + if (polygon.interpUV) { + fpolygon.vertices[j][U] = polyVertices[i+j][U]; + fpolygon.vertices[j][V] = polyVertices[i+j][V]; + } + } + fpolygon.render(); + } + } + if (stroke) { + // first draw all vertices as a line strip + if (shape == TRIANGLE_STRIP) { + draw_lines(polyVertices, polyVertexCount-1, 1, 1, 0); + } else { + draw_lines(polyVertices, polyVertexCount-1, 1, 1, 3); + } + // then draw from vertex (n) to (n+2) + // incrementing n using the same as above + draw_lines(polyVertices, polyVertexCount-2, 2, increment, 0); + // changed this to vertexCount-2, because it seemed + // to be adding an extra (nonexistant) line + } + break; + + case QUADS: + case QUAD_STRIP: + //System.out.println("pooping out a quad"); + increment = (shape == QUADS) ? 4 : 2; + if (fill) { + fpolygon.vertexCount = 4; + for (int i = 0; i < polyVertexCount-3; i += increment) { + for (int j = 0; j < 4; j++) { + fpolygon.vertices[j][R] = polyVertices[i+j][R]; + fpolygon.vertices[j][G] = polyVertices[i+j][G]; + fpolygon.vertices[j][B] = polyVertices[i+j][B]; + fpolygon.vertices[j][A] = polyVertices[i+j][A]; + + fpolygon.vertices[j][X] = polyVertices[i+j][X]; + fpolygon.vertices[j][Y] = polyVertices[i+j][Y]; + fpolygon.vertices[j][Z] = polyVertices[i+j][Z]; + + if (polygon.interpUV) { + fpolygon.vertices[j][U] = polyVertices[i+j][U]; + fpolygon.vertices[j][V] = polyVertices[i+j][V]; + } + } + fpolygon.render(); + } + } + if (stroke) { + // first draw all vertices as a line strip + if (shape == QUAD_STRIP) { + draw_lines(polyVertices, polyVertexCount-1, 1, 1, 0); + } else { // skip every few for quads + draw_lines(polyVertices, polyVertexCount, 1, 1, 4); + } + // then draw from vertex (n) to (n+3) + // incrementing n by the same increment as above + draw_lines(polyVertices, polyVertexCount-2, 3, increment, 0); + } + break; + + case POLYGON: + if (isConvex()) { + if (fill) { + polygon.render(); + if (stroke) polygon.unexpand(); + } + + if (stroke) { + draw_lines(polyVertices, polyVertexCount-1, 1, 1, 0); + // draw the last line connecting back to the first point in poly + svertices[0] = polyVertices[polyVertexCount-1]; + svertices[1] = polyVertices[0]; + draw_lines(svertices, 1, 1, 1, 0); + } + } else { + if (fill) { + // the triangulator produces polygons that don't align + // when smoothing is enabled. but if there is a stroke around + // the polygon, then smoothing can be temporarily disabled. + boolean smoov = smooth; + //if (stroke && !hints[DISABLE_SMOOTH_HACK]) smooth = false; + if (stroke) smooth = false; + concaveRender(); + //if (stroke && !hints[DISABLE_SMOOTH_HACK]) smooth = smoov; + if (stroke) smooth = smoov; + } + + if (stroke) { + draw_lines(polyVertices, polyVertexCount-1, 1, 1, 0); + // draw the last line connecting back + // to the first point in poly + svertices[0] = polyVertices[polyVertexCount-1]; + svertices[1] = polyVertices[0]; + draw_lines(svertices, 1, 1, 1, 0); + } + } + break; + } + + // to signify no shape being drawn + shape = 0; + } + + + + ////////////////////////////////////////////////////////////// + + // CONCAVE/CONVEX POLYGONS + + + private boolean isConvex() { + float v[][] = polygon.vertices; + int n = polygon.vertexCount; + int j,k; + int flag = 0; + float z; + //float tol = 0.001f; + + if (n < 3) + // ERROR: this is a line or a point, render with CONVEX + return true; + + // iterate along border doing dot product. + // if the sign of the result changes, then is concave + for (int i=0;i 0) + flag |= 2; + if (flag == 3) + return false; // CONCAVE + } + if (flag != 0) + return true; // CONVEX + else + // ERROR: colinear points, self intersection + // treat as CONVEX + return true; + } + + + // triangulate the current polygon + private void concaveRender() { + // WARNING: code is not in optimum form + // local initiations of some variables are made to + // keep the code modular and easy to integrate + // restet triangle + float polyVertices[][] = polygon.vertices; + + if (tpolygon == null) { + // allocate on first use, rather than slowing + // the startup of the class. + tpolygon = new PPolygon(this); + tpolygon_vertex_order = new int[TPOLYGON_MAX_VERTICES]; + } + tpolygon.reset(3); + + // copy render parameters + + if (textureImage != null) { + tpolygon.texture(textureImage); //polygon.timage); + } + + tpolygon.interpX = polygon.interpX; + tpolygon.interpZ = polygon.interpZ; + tpolygon.interpUV = polygon.interpUV; + tpolygon.interpARGB = polygon.interpARGB; + + // simple ear clipping polygon triangulation + // addapted from code by john w. ratcliff (jratcliff@verant.com) + + // 1 - first we check if the polygon goes CW or CCW + // CW-CCW ordering adapted from code by + // Joseph O'Rourke orourke@cs.smith.edu + // 1A - we start by finding the lowest-right most vertex + + boolean ccw = false; // clockwise + + int n = polygon.vertexCount; + int mm; // postion for LR vertex + float min[] = new float[2]; + + min[X] = polyVertices[0][X]; + min[Y] = polyVertices[0][Y]; + mm = 0; + + for(int i = 0; i < n; i++ ) { + if( (polyVertices[i][Y] < min[Y]) || + ( (polyVertices[i][Y] == min[Y]) && (polyVertices[i][X] > min[X]) ) + ) { + mm = i; + min[X] = polyVertices[mm][X]; + min[Y] = polyVertices[mm][Y]; + } + } + + // 1B - now we compute the cross product of the edges of this vertex + float cp; + int mm1; + + // just for renaming + float a[] = new float[2]; + float b[] = new float[2]; + float c[] = new float[2]; + + mm1 = (mm + (n-1)) % n; + + // assign a[0] to point to poly[m1][0] etc. + for(int i = 0; i < 2; i++ ) { + a[i] = polyVertices[mm1][i]; + b[i] = polyVertices[mm][i]; + c[i] = polyVertices[(mm+1)%n][i]; + } + + cp = a[0] * b[1] - a[1] * b[0] + + a[1] * c[0] - a[0] * c[1] + + b[0] * c[1] - c[0] * b[1]; + + if ( cp > 0 ) + ccw = true; // CCW + else + ccw = false; // CW + + // 1C - then we sort the vertices so they + // are always in a counterclockwise order + //int j = 0; + if (!ccw) { + // keep the same order + for (int i = 0; i < n; i++) { + tpolygon_vertex_order[i] = i; + } + + } else { + // invert the order + for (int i = 0; i < n; i++) { + tpolygon_vertex_order[i] = (n - 1) - i; + } + } + + // 2 - begin triangulation + // resulting triangles are stored in the triangle array + // remove vc-2 Vertices, creating 1 triangle every time + int vc = n; + int count = 2*vc; // complex polygon detection + + for (int m = 0, v = vc - 1; vc > 2; ) { + boolean snip = true; + + // if we start over again, is a complex polygon + if (0 >= (count--)) { + break; // triangulation failed + } + + // get 3 consecutive vertices + int u = v ; if (vc <= u) u = 0; // previous + v = u+1; if (vc <= v) v = 0; // current + int w = v+1; if (vc <= w) w = 0; // next + + // triangle A B C + float Ax, Ay, Bx, By, Cx, Cy, Px, Py; + + Ax = -polyVertices[tpolygon_vertex_order[u]][X]; + Ay = polyVertices[tpolygon_vertex_order[u]][Y]; + Bx = -polyVertices[tpolygon_vertex_order[v]][X]; + By = polyVertices[tpolygon_vertex_order[v]][Y]; + Cx = -polyVertices[tpolygon_vertex_order[w]][X]; + Cy = polyVertices[tpolygon_vertex_order[w]][Y]; + + if ( EPSILON > (((Bx-Ax) * (Cy-Ay)) - ((By-Ay) * (Cx-Ax)))) { + continue; + } + + for (int p = 0; p < vc; p++) { + + // this part is a bit osbscure, basically what it does + // is test if this tree vertices are and ear or not, looking for + // intersections with the remaining vertices using a cross product + float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; + float cCROSSap, bCROSScp, aCROSSbp; + + if( (p == u) || (p == v) || (p == w) ) { + continue; + } + + Px = -polyVertices[tpolygon_vertex_order[p]][X]; + Py = polyVertices[tpolygon_vertex_order[p]][Y]; + + ax = Cx - Bx; ay = Cy - By; + bx = Ax - Cx; by = Ay - Cy; + cx = Bx - Ax; cy = By - Ay; + apx= Px - Ax; apy= Py - Ay; + bpx= Px - Bx; bpy= Py - By; + cpx= Px - Cx; cpy= Py - Cy; + + aCROSSbp = ax * bpy - ay * bpx; + cCROSSap = cx * apy - cy * apx; + bCROSScp = bx * cpy - by * cpx; + + if ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)) { + snip = false; + } + } + + if (snip) { + // yes, the trio is an ear, render it and cut it + + int triangle_vertices[] = new int[3]; + int s,t; + + // true names of the vertices + triangle_vertices[0] = tpolygon_vertex_order[u]; + triangle_vertices[1] = tpolygon_vertex_order[v]; + triangle_vertices[2] = tpolygon_vertex_order[w]; + + // create triangle + //render_triangle(triangle_vertices); + //private final void render_triangle(int[] triangle_vertices) { + // copy all fields of the triangle vertices + for (int i = 0; i < 3; i++) { + float[] src = polygon.vertices[triangle_vertices[i]]; + float[] dest = tpolygon.vertices[i]; + for (int k = 0; k < VERTEX_FIELD_COUNT; k++) { + dest[k] = src[k]; + } + } + // render triangle + tpolygon.render(); + //} + + m++; + + // remove v from remaining polygon + for( s = v, t = v + 1; t < vc; s++, t++) { + tpolygon_vertex_order[s] = tpolygon_vertex_order[t]; + } + + vc--; + + // resest error detection counter + count = 2 * vc; + } + } + } + + + + ////////////////////////////////////////////////////////////// + + // RECT + + + protected void rectImpl(float x1f, float y1f, float x2f, float y2f) { + + if (untransformed() && !fillAlpha) { + int x1 = (int) x1f; + int y1 = (int) y1f; + int x2 = (int) x2f; + int y2 = (int) y2f; + + rectImplFillUntranSolidRGB(x1, y1, x2, y2); + + if (stroke) { + if (strokeWeight == 1) { + thin_flat_line(x1, y1, x2, y1); + thin_flat_line(x2, y1, x2, y2); + thin_flat_line(x2, y2, x1, y2); + thin_flat_line(x1, y2, x1, y1); + + } else { + thick_flat_line(x1, y1, fillR, fillG, fillB, fillA, + x2, y1, fillR, fillG, fillB, fillA); + thick_flat_line(x2, y1, fillR, fillG, fillB, fillA, + x2, y2, fillR, fillG, fillB, fillA); + thick_flat_line(x2, y2, fillR, fillG, fillB, fillA, + x1, y2, fillR, fillG, fillB, fillA); + thick_flat_line(x1, y2, fillR, fillG, fillB, fillA, + x1, y1, fillR, fillG, fillB, fillA); + } + } + + } else { + beginShape(QUADS); + vertex(x1f, y1f); + vertex(x2f, y1f); + vertex(x2f, y2f); + vertex(x1f, y2f); + endShape(); + } + } + + + /** + * Draw an untransformed rectangle with no alpha. + */ + private void rectImplFillUntranSolidRGB(int x1, int y1, int x2, int y2) { + //System.out.println("flat quad"); + if (y2 < y1) { + int temp = y1; y1 = y2; y2 = temp; + } + if (x2 < x1) { + int temp = x1; x1 = x2; x2 = temp; + } + // checking to watch out for boogers + if ((x1 > width1) || (x2 < 0) || + (y1 > height1) || (y2 < 0)) return; + + //if (fill) { + int fx1 = x1; + int fy1 = y1; + int fx2 = x2; + int fy2 = y2; + + // these only affect the fill, not the stroke + // (otherwise strange boogers at edges b/c frame changes shape) + if (fx1 < 0) fx1 = 0; + if (fx2 > width) fx2 = width; + if (fy1 < 0) fy1 = 0; + if (fy2 > height) fy2 = height; + + // [toxi 031223] + // on avg. 20-25% faster fill routine using System.arraycopy() + int ww = fx2 - fx1; + int hh = fy2 - fy1; + int[] row = new int[ww]; + for (int i = 0; i < ww; i++) row[i] = fillColor; + int idx = fy1 * width + fx1; + for (int y = 0; y < hh; y++) { + System.arraycopy(row, 0, pixels, idx, ww); + idx += width; + } + row = null; + //} + } + + + + ////////////////////////////////////////////////////////////// + + // ELLIPSE AND ARC + + + public void ellipseImpl(float x1, float y1, float w, float h) { + if (!smooth && (strokeWeight == 1) && + !fillAlpha && !strokeAlpha && untransformed()) { + float hradius = w / 2f; + float vradius = h / 2f; + + int centerX = (int) (x1 + hradius); + int centerY = (int) (y1 + vradius); + + if (hradius == vradius) { + flat_circle(centerX, centerY, (int)hradius); + + } else { + flat_ellipse(centerX, centerY, (int)hradius, (int)vradius); + } + } else { + super.ellipseImpl(x1, y1, w, h); + } + } + + + private void flat_circle(int centerX, int centerY, int radius) { + if (unwarped()) { + float x = m00*centerX + m01*centerY + m02; + float y = m10*centerX + m11*centerY + m12; + centerX = (int)x; + centerY = (int)y; + } + if (fill) flat_circle_fill(centerX, centerY, radius); + if (stroke) flat_circle_stroke(centerX, centerY, radius); + } + + + /** + * Draw the outline around a flat circle using a bresenham-style + * algorithm. Adapted from drawCircle function in "Computer Graphics + * for Java Programmers" by Leen Ammeraal, p. 110. + *

+ * This function is included because the quality is so much better, + * and the drawing significantly faster than with adaptive ellipses + * drawn using the sine/cosine tables. + *

+ * Circle quadrants break down like so: + *

+   *              |
+   *        \ NNW | NNE /
+   *          \   |   /
+   *       WNW  \ | /  ENE
+   *     -------------------
+   *       WSW  / | \  ESE
+   *          /   |   \
+   *        / SSW | SSE \
+   *              |
+   * 
+ * @param xc x center + * @param yc y center + * @param r radius + */ + private void flat_circle_stroke(int xC, int yC, int r) { + int x = 0, y = r, u = 1, v = 2 * r - 1, E = 0; + while (x < y) { + thin_point(xC + x, yC + y, 0, strokeColor); // NNE + thin_point(xC + y, yC - x, 0, strokeColor); // ESE + thin_point(xC - x, yC - y, 0, strokeColor); // SSW + thin_point(xC - y, yC + x, 0, strokeColor); // WNW + + x++; E += u; u += 2; + if (v < 2 * E) { + y--; E -= v; v -= 2; + } + if (x > y) break; + + thin_point(xC + y, yC + x, 0, strokeColor); // ENE + thin_point(xC + x, yC - y, 0, strokeColor); // SSE + thin_point(xC - y, yC - x, 0, strokeColor); // WSW + thin_point(xC - x, yC + y, 0, strokeColor); // NNW + } + } + + /** + * Heavily adapted version of the above algorithm that handles + * filling the ellipse. Works by drawing from the center and + * outwards to the points themselves. Has to be done this way + * because the values for the points are changed halfway through + * the function, making it impossible to just store a series of + * left and right edges to be drawn more quickly. + * + * @param xc x center + * @param yc y center + * @param r radius + */ + private void flat_circle_fill(int xc, int yc, int r) { + int x = 0, y = r, u = 1, v = 2 * r - 1, E = 0; + while (x < y) { + for (int xx = xc; xx < xc + x; xx++) { // NNE + thin_point(xx, yc + y, 0, fillColor); + } + for (int xx = xc; xx < xc + y; xx++) { // ESE + thin_point(xx, yc - x, 0, fillColor); + } + for (int xx = xc - x; xx < xc; xx++) { // SSW + thin_point(xx, yc - y, 0, fillColor); + } + for (int xx = xc - y; xx < xc; xx++) { // WNW + thin_point(xx, yc + x, 0, fillColor); + } + + x++; E += u; u += 2; + if (v < 2 * E) { + y--; E -= v; v -= 2; + } + if (x > y) break; + + for (int xx = xc; xx < xc + y; xx++) { // ENE + thin_point(xx, yc + x, 0, fillColor); + } + for (int xx = xc; xx < xc + x; xx++) { // SSE + thin_point(xx, yc - y, 0, fillColor); + } + for (int xx = xc - y; xx < xc; xx++) { // WSW + thin_point(xx, yc - x, 0, fillColor); + } + for (int xx = xc - x; xx < xc; xx++) { // NNW + thin_point(xx, yc + y, 0, fillColor); + } + } + } + + // unfortunately this can't handle fill and stroke simultaneously, + // because the fill will later replace some of the stroke points + + private final void flat_ellipse_symmetry(int centerX, int centerY, + int ellipseX, int ellipseY, + boolean filling) { + if (filling) { + for (int i = centerX - ellipseX + 1; i < centerX + ellipseX; i++) { + thin_point(i, centerY - ellipseY, 0, fillColor); + thin_point(i, centerY + ellipseY, 0, fillColor); + } + } else { + thin_point(centerX - ellipseX, centerY + ellipseY, 0, strokeColor); + thin_point(centerX + ellipseX, centerY + ellipseY, 0, strokeColor); + thin_point(centerX - ellipseX, centerY - ellipseY, 0, strokeColor); + thin_point(centerX + ellipseX, centerY - ellipseY, 0, strokeColor); + } + } + + + /** + * Bresenham-style ellipse drawing function, adapted from a posting to + * comp.graphics.algortihms. + * + * This function is included because the quality is so much better, + * and the drawing significantly faster than with adaptive ellipses + * drawn using the sine/cosine tables. + * + * @param centerX x coordinate of the center + * @param centerY y coordinate of the center + * @param a horizontal radius + * @param b vertical radius + */ + private void flat_ellipse_internal(int centerX, int centerY, + int a, int b, boolean filling) { + int x, y, a2, b2, s, t; + + a2 = a*a; + b2 = b*b; + x = 0; + y = b; + s = a2*(1-2*b) + 2*b2; + t = b2 - 2*a2*(2*b-1); + flat_ellipse_symmetry(centerX, centerY, x, y, filling); + + do { + if (s < 0) { + s += 2*b2*(2*x+3); + t += 4*b2*(x+1); + x++; + } else if (t < 0) { + s += 2*b2*(2*x+3) - 4*a2*(y-1); + t += 4*b2*(x+1) - 2*a2*(2*y-3); + x++; + y--; + } else { + s -= 4*a2*(y-1); + t -= 2*a2*(2*y-3); + y--; + } + flat_ellipse_symmetry(centerX, centerY, x, y, filling); + + } while (y > 0); + } + + + private void flat_ellipse(int centerX, int centerY, int a, int b) { + if (unwarped()) { + float x = m00*centerX + m01*centerY + m02; + float y = m10*centerX + m11*centerY + m12; + centerX = (int)x; + centerY = (int)y; + } + if (fill) flat_ellipse_internal(centerX, centerY, a, b, true); + if (stroke) flat_ellipse_internal(centerX, centerY, a, b, false); + } + + + // TODO really need a decent arc function in here.. + + //protected void arcImpl(float x1, float y1, float w, float h, + // float start, float stop) + + + + ////////////////////////////////////////////////////////////// + + // BOX & SPHERE + + + // The PGraphics superclass will throw errors for these fellas + + + + ////////////////////////////////////////////////////////////// + + // BEZIER & CURVE + + + public void bezier(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + depthErrorXYZ("bezier"); + } + + + public void curve(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + depthErrorXYZ("curve"); + } + + + + ////////////////////////////////////////////////////////////// + + // IMAGE + + + protected void imageImpl(PImage image, + float x1, float y1, float x2, float y2, + int u1, int v1, int u2, int v2) { + if ((x2 - x1 == image.width) && + (y2 - y1 == image.height) && + !tint && unwarped()) { + flat_image(image, (int) (x1 + m02), (int) (y1 + m12), u1, v1, u2, v2); + + } else { + super.imageImpl(image, x1, y1, x2, y2, u1, v1, u2, v2); + } + } + + + /** + * Image drawn in flat "screen space", with no scaling or warping. + * this is so common that a special routine is included for it, + * because the alternative is much slower. + * + * @param image image to be drawn + * @param sx1 x coordinate of upper-lefthand corner in screen space + * @param sy1 y coordinate of upper-lefthand corner in screen space + */ + private void flat_image(PImage image, int sx1, int sy1, + int ix1, int iy1, int ix2, int iy2) { + /* + int ix1 = 0; + int iy1 = 0; + int ix2 = image.width; + int iy2 = image.height; + */ + + if (imageMode == CENTER) { + sx1 -= image.width / 2; + sy1 -= image.height / 2; + } + + int sx2 = sx1 + image.width; + int sy2 = sy1 + image.height; + + // don't draw if completely offscreen + // (without this check, ArrayIndexOutOfBoundsException) + if ((sx1 > width1) || (sx2 < 0) || + (sy1 > height1) || (sy2 < 0)) return; + + if (sx1 < 0) { // off left edge + ix1 -= sx1; + sx1 = 0; + } + if (sy1 < 0) { // off top edge + iy1 -= sy1; + sy1 = 0; + } + if (sx2 > width) { // off right edge + ix2 -= sx2 - width; + sx2 = width; + } + if (sy2 > height) { // off bottom edge + iy2 -= sy2 - height; + sy2 = height; + } + + int source = iy1 * image.width + ix1; + int target = sy1 * width; + + if (image.format == ARGB) { + for (int y = sy1; y < sy2; y++) { + int tx = 0; + + for (int x = sx1; x < sx2; x++) { + pixels[target + x] = + _blend(pixels[target + x], + image.pixels[source + tx], + image.pixels[source + tx++] >>> 24); + } + source += image.width; + target += width; + } + } else if (image.format == ALPHA) { + for (int y = sy1; y < sy2; y++) { + int tx = 0; + + for (int x = sx1; x < sx2; x++) { + pixels[target + x] = + _blend(pixels[target + x], + fillColor, + image.pixels[source + tx++]); + } + source += image.width; + target += width; + } + + } else if (image.format == RGB) { + target += sx1; + int tw = sx2 - sx1; + for (int y = sy1; y < sy2; y++) { + System.arraycopy(image.pixels, source, pixels, target, tw); + // should set z coordinate in here + // or maybe not, since dims=0, meaning no relevant z + source += image.width; + target += width; + } + } + } + + + ////////////////////////////////////////////////////////////// + + // TEXT/FONTS + + + // These will be handled entirely by PGraphics. + + + + ////////////////////////////////////////////////////////////// + + + // expects properly clipped coords, hence does + // NOT check if x/y are in bounds [toxi] + private void thin_pointAt(int x, int y, float z, int color) { + int index = y*width+x; // offset values are pre-calced in constructor + pixels[index] = color; + zbuffer[index] = z; + } + + // expects offset/index in pixelbuffer array instead of x/y coords + // used by optimized parts of thin_flat_line() [toxi] + private void thin_pointAtIndex(int offset, float z, int color) { + pixels[offset] = color; + zbuffer[offset] = z; + } + + // points are inherently flat, but always tangent + // to the screen surface. the z is only so that things + // get scaled properly if the pt is way in back + private void thick_point(float x, float y, float z, // note floats + float r, float g, float b, float a) { + spolygon.reset(4); + spolygon.interpARGB = false; // no changes for vertices of a point + + float strokeWidth2 = strokeWeight/2.0f; + + float svertex[] = spolygon.vertices[0]; + svertex[X] = x - strokeWidth2; + svertex[Y] = y - strokeWidth2; + svertex[Z] = z; + + svertex[R] = r; + svertex[G] = g; + svertex[B] = b; + svertex[A] = a; + + svertex = spolygon.vertices[1]; + svertex[X] = x + strokeWidth2; + svertex[Y] = y - strokeWidth2; + svertex[Z] = z; + + svertex = spolygon.vertices[2]; + svertex[X] = x + strokeWidth2; + svertex[Y] = y + strokeWidth2; + svertex[Z] = z; + + svertex = spolygon.vertices[3]; + svertex[X] = x - strokeWidth2; + svertex[Y] = y + strokeWidth2; + svertex[Z] = z; + + spolygon.render(); + } + + + // new bresenham clipping code, as old one was buggy [toxi] + private void thin_flat_line(int x1, int y1, int x2, int y2) { + int nx1,ny1,nx2,ny2; + + // get the "dips" for the points to clip + int code1 = thin_flat_lineClipCode(x1, y1); + int code2 = thin_flat_lineClipCode(x2, y2); + + if ((code1 & code2)!=0) { + return; + } else { + int dip = code1 | code2; + if (dip != 0) { + // now calculate the clipped points + float a1 = 0, a2 = 1, a = 0; + for (int i=0;i<4;i++) { + if (((dip>>i)%2)==1) { + a = thin_flat_lineSlope((float)x1, (float)y1, + (float)x2, (float)y2, i+1); + if (((code1>>i)%2)==1) { + a1 = (float)Math.max(a, a1); + } else { + a2 = (float)Math.min(a, a2); + } + } + } + if (a1>a2) return; + else { + nx1=(int) (x1+a1*(x2-x1)); + ny1=(int) (y1+a1*(y2-y1)); + nx2=(int) (x1+a2*(x2-x1)); + ny2=(int) (y1+a2*(y2-y1)); + } + // line is fully visible/unclipped + } else { + nx1=x1; nx2=x2; + ny1=y1; ny2=y2; + } + } + + // new "extremely fast" line code + // adapted from http://www.edepot.com/linee.html + + boolean yLonger=false; + int shortLen=ny2-ny1; + int longLen=nx2-nx1; + if (Math.abs(shortLen)>Math.abs(longLen)) { + int swap=shortLen; + shortLen=longLen; + longLen=swap; + yLonger=true; + } + int decInc; + if (longLen==0) decInc=0; + else decInc = (shortLen << 16) / longLen; + + if (nx1==nx2) { + // special case: vertical line + if (ny1>ny2) { int ty=ny1; ny1=ny2; ny2=ty; } + int offset=ny1*width+nx1; + for(int j=ny1; j<=ny2; j++) { + thin_pointAtIndex(offset,0,strokeColor); + offset+=width; + } + return; + } else if (ny1==ny2) { + // special case: horizontal line + if (nx1>nx2) { int tx=nx1; nx1=nx2; nx2=tx; } + int offset=ny1*width+nx1; + for(int j=nx1; j<=nx2; j++) thin_pointAtIndex(offset++,0,strokeColor); + return; + } else if (yLonger) { + if (longLen>0) { + longLen+=ny1; + for (int j=0x8000+(nx1<<16);ny1<=longLen;++ny1) { + thin_pointAt(j>>16, ny1, 0, strokeColor); + j+=decInc; + } + return; + } + longLen+=ny1; + for (int j=0x8000+(nx1<<16);ny1>=longLen;--ny1) { + thin_pointAt(j>>16, ny1, 0, strokeColor); + j-=decInc; + } + return; + } else if (longLen>0) { + longLen+=nx1; + for (int j=0x8000+(ny1<<16);nx1<=longLen;++nx1) { + thin_pointAt(nx1, j>>16, 0, strokeColor); + j+=decInc; + } + return; + } + longLen+=nx1; + for (int j=0x8000+(ny1<<16);nx1>=longLen;--nx1) { + thin_pointAt(nx1, j>>16, 0, strokeColor); + j-=decInc; + } + } + + private int thin_flat_lineClipCode(float x, float y) { + return ((y < 0 ? 8 : 0) | (y > height1 ? 4 : 0) | + (x < 0 ? 2 : 0) | (x > width1 ? 1 : 0)); + } + + private float thin_flat_lineSlope(float x1, float y1, + float x2, float y2, int border) { + switch (border) { + case 4: { + return (-y1)/(y2-y1); + } + case 3: { + return (height1-y1)/(y2-y1); + } + case 2: { + return (-x1)/(x2-x1); + } + case 1: { + return (width1-x1)/(x2-x1); + } + } + return -1f; + } + + + private boolean flat_line_retribution(float x1, float y1, + float x2, float y2, + float r1, float g1, float b1) { + /* + // assume that if it is/isn't big in one dir, then the + // other doesn't matter, cuz that's a weird case + float lwidth = m00*strokeWeight + m01*strokeWeight; + //float lheight = m10*strokeWeight + m11*strokeWeight; + // lines of stroke thickness 1 can be anywhere from -1.41 to 1.41 + if ((strokeWeight < TWO) && (!hints[SCALE_STROKE_WIDTH])) { + //if (abs(lwidth) < 1.5f) { + //System.out.println("flat line retribution " + r1 + " " + g1 + " " + b1); + int strokeSaved = strokeColor; + strokeColor = float_color(r1, g1, b1); + thin_flat_line((int)x1, (int)y1, (int)x2, (int)y2); + strokeColor = strokeSaved; + return true; + } + */ + return false; + } + + + private void thick_flat_line(float ox1, float oy1, + float r1, float g1, float b1, float a1, + float ox2, float oy2, + float r2, float g2, float b2, float a2) { + spolygon.interpARGB = (r1 != r2) || (g1 != g2) || (b1 != b2) || (a1 != a2); + spolygon.interpZ = false; + + if (!spolygon.interpARGB && + flat_line_retribution(ox1, oy1, ox2, oy2, r1, g1, b1)) { + return; + } + + float dX = ox2-ox1 + EPSILON; + float dY = oy2-oy1 + EPSILON; + float len = sqrt(dX*dX + dY*dY); + + // TODO strokeWidth should be transformed! + float rh = strokeWeight / len; + + float dx0 = rh * dY; + float dy0 = rh * dX; + float dx1 = rh * dY; + float dy1 = rh * dX; + + spolygon.reset(4); + + float svertex[] = spolygon.vertices[0]; + svertex[X] = ox1+dx0; + svertex[Y] = oy1-dy0; + svertex[R] = r1; + svertex[G] = g1; + svertex[B] = b1; + svertex[A] = a1; + + svertex = spolygon.vertices[1]; + svertex[X] = ox1-dx0; + svertex[Y] = oy1+dy0; + svertex[R] = r1; + svertex[G] = g1; + svertex[B] = b1; + svertex[A] = a1; + + svertex = spolygon.vertices[2]; + svertex[X] = ox2-dx1; + svertex[Y] = oy2+dy1; + svertex[R] = r2; + svertex[G] = g2; + svertex[B] = b2; + svertex[A] = a2; + + svertex = spolygon.vertices[3]; + svertex[X] = ox2+dx1; + svertex[Y] = oy2-dy1; + svertex[R] = r2; + svertex[G] = g2; + svertex[B] = b2; + svertex[A] = a2; + + spolygon.render(); + } + + + /* + // OPT version without z coords can save 8 multiplies and some other + private void spatial_line(float x1, float y1, + float r1, float g1, float b1, + float x2, float y2, + float r2, float g2, float b2) { + spatial_line(x1, y1, 0, r1, g1, b1, + x2, y2, 0, r2, g2, b2); + } + + + // the incoming values are transformed, + // and the colors have been calculated + + private void spatial_line(float x1, float y1, float z1, + float r1, float g1, float b1, + float x2, float y2, float z2, + float r2, float g2, float b2) { + spolygon.interpARGB = (r1 != r2) || (g1 != g2) || (b1 != b2); + if (!spolygon.interpARGB && + flat_line_retribution(x1, y1, x2, y2, r1, g1, b1)) { + return; + } + + spolygon.interpZ = true; + + float ox1 = x1; float oy1 = y1; float oz1 = z1; + float ox2 = x2; float oy2 = y2; float oz2 = z2; + + float dX = ox2-ox1 + 0.0001f; + float dY = oy2-oy1 + 0.0001f; + float len = sqrt(dX*dX + dY*dY); + + //float x0 = m00*0 + m01*0 + m03; + + float rh = strokeWeight / len; + + float dx0 = rh * dY; + float dy0 = rh * dX; + float dx1 = rh * dY; + float dy1 = rh * dX; + + spolygon.reset(4); + + float svertex[] = spolygon.vertices[0]; + svertex[X] = ox1+dx0; + svertex[Y] = oy1-dy0; + svertex[Z] = oz1; + svertex[R] = r1; //calcR1; + svertex[G] = g1; //calcG1; + svertex[B] = b1; //calcB1; + + svertex = spolygon.vertices[1]; + svertex[X] = ox1-dx0; + svertex[Y] = oy1+dy0; + svertex[Z] = oz1; + svertex[R] = r1; //calcR1; + svertex[G] = g1; //calcG1; + svertex[B] = b1; //calcB1; + + svertex = spolygon.vertices[2]; + svertex[X] = ox2-dx1; + svertex[Y] = oy2+dy1; + svertex[Z] = oz2; + svertex[R] = r2; //calcR2; + svertex[G] = g2; //calcG2; + svertex[B] = b2; //calcB2; + + svertex = spolygon.vertices[3]; + svertex[X] = ox2+dx1; + svertex[Y] = oy2-dy1; + svertex[Z] = oz2; + svertex[R] = r2; //calcR2; + svertex[G] = g2; //calcG2; + svertex[B] = b2; //calcB2; + + spolygon.render(); + } + */ + + + // max is what to count to + // offset is offset to the 'next' vertex + // increment is how much to increment in the loop + private void draw_lines(float vertices[][], int max, + int offset, int increment, int skip) { + + if (strokeWeight < 2) { + for (int i = 0; i < max; i += increment) { + if ((skip != 0) && (((i+offset) % skip) == 0)) continue; + + float a[] = vertices[i]; + float b[] = vertices[i+offset]; + + if (line == null) line = new PLine(this); + + line.reset(); + line.setIntensities(a[SR], a[SG], a[SB], a[SA], + b[SR], b[SG], b[SB], b[SA]); + line.setVertices(a[X], a[Y], a[Z], + b[X], b[Y], b[Z]); + line.draw(); + } + + } else { // use old line code for thickness > 1 + + if ((strokeWeight < 2) && !strokeChanged) { + // need to set color at least once? + + // THIS PARTICULAR CASE SHOULD NO LONGER BE REACHABLE + + for (int i = 0; i < max; i += increment) { + if ((skip != 0) && (((i+offset) % skip) == 0)) continue; + thin_flat_line((int) vertices[i][X], + (int) vertices[i][Y], + (int) vertices[i+offset][X], + (int) vertices[i+offset][Y]); + } + } else { + for (int i = 0; i < max; i += increment) { + if ((skip != 0) && (((i+offset) % skip) == 0)) continue; + float v1[] = vertices[i]; + float v2[] = vertices[i+offset]; + thick_flat_line(v1[X], v1[Y], v1[SR], v1[SG], v1[SB], v1[SA], + v2[X], v2[Y], v2[SR], v2[SG], v2[SB], v2[SA]); + } + } + } + } + + + + ////////////////////////////////////////////////////////////// + + // UGLY RENDERING SHIT + + + private void thin_point(int x, int y, float z, int color) { + // necessary? [fry] yes! [toxi] + if (x<0 || x>width1 || y<0 || y>height1) return; + + int index = y*width + x; + if ((color & 0xff000000) == 0xff000000) { // opaque + pixels[index] = color; + + } else { // transparent + // couldn't seem to get this working correctly + + //pixels[index] = _blend(pixels[index], + // color & 0xffffff, (color >> 24) & 0xff); + + // a1 is how much of the orig pixel + int a2 = (color >> 24) & 0xff; + int a1 = a2 ^ 0xff; + + int p2 = strokeColor; + int p1 = pixels[index]; + + int r = (a1 * ((p1 >> 16) & 0xff) + a2 * ((p2 >> 16) & 0xff)) & 0xff00; + int g = (a1 * ((p1 >> 8) & 0xff) + a2 * ((p2 >> 8) & 0xff)) & 0xff00; + int b = (a1 * ( p1 & 0xff) + a2 * ( p2 & 0xff)) >> 8; + + pixels[index] = 0xff000000 | (r << 8) | g | b; + + //pixels[index] = _blend(pixels[index], + // color & 0xffffff, (color >> 24) & 0xff); + /* + pixels[index] = 0xff000000 | + ((((a1 * ((pixels[index] >> 16) & 0xff) + + a2 * ((color >> 16) & 0xff)) & 0xff00) << 24) << 8) | + (((a1 * ((pixels[index] >> 8) & 0xff) + + a2 * ((color >> 8) & 0xff)) & 0xff00) << 16) | + (((a1 * ( pixels[index] & 0xff) + + a2 * ( color & 0xff)) >> 8)); + */ + } + zbuffer[index] = z; + } + + + + ////////////////////////////////////////////////////////////// + + // BACKGROUND AND FRIENDS + + + /** + * Clear the pixel buffer. + */ + protected void clear() { + for (int i = 0; i < pixelCount; i++) { + pixels[i] = backgroundColor; + } + } + + + + ////////////////////////////////////////////////////////////// + + // INTERNAL SCHIZZLE + + + private boolean untransformed() { + return ((m00 == 1) && (m01 == 0) && (m02 == 0) && + (m10 == 0) && (m11 == 1) && (m12 == 0)); + } + + + private boolean unwarped() { + return ((m00 == 1) && (m01 == 0) && (m10 == 0) && (m11 == 1)); + } + + + + // doesn't really do lighting per se... + private void calc_lighting(float r, float g, float b, + float ix, float iy, float iz, + float nx, float ny, float nz, + float target[], int toffset) { + target[toffset + 0] = r; + target[toffset + 1] = g; + target[toffset + 2] = b; + } + + + static private final int float_color(float r, float g, float b) { + return (0xff000000 | + ((int) (255.0f * r)) << 16 | + ((int) (255.0f * g)) << 8 | + ((int) (255.0f * b))); + } + + public final static int _blend(int p1, int p2, int a2) { + // scale alpha by alpha of incoming pixel + a2 = (a2 * (p2 >>> 24)) >> 8; + + int a1 = a2 ^ 0xff; + int r = (a1 * ((p1 >> 16) & 0xff) + a2 * ((p2 >> 16) & 0xff)) & 0xff00; + int g = (a1 * ((p1 >> 8) & 0xff) + a2 * ((p2 >> 8) & 0xff)) & 0xff00; + int b = (a1 * ( p1 & 0xff) + a2 * ( p2 & 0xff)) >> 8; + + return 0xff000000 | (r << 8) | g | b; + } +} diff --git a/core/PGraphics3D.java b/core/PGraphics3D.java new file mode 100644 index 000000000..40ec5d711 --- /dev/null +++ b/core/PGraphics3D.java @@ -0,0 +1,3943 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.Toolkit; +import java.awt.image.DirectColorModel; +import java.awt.image.MemoryImageSource; + + +/** + * Subclass of PGraphics that handles 3D rendering for Java 1.1. + * It can render 3D inside a browser window and requires no plug-ins. + *

+ * The renderer is mostly set up based on the structure of the OpenGL API, + * if you have questions about specifics that aren't covered here, + * look for reference on the OpenGL implementation of a similar feature. + *

+ * Lighting and camera implementation by Simon Greenwold. + */ +public class PGraphics3D extends PGraphics { + + // ........................................................ + + // Lighting-related variables + + // Whether or not we have to worry about vertex position for lighting calcs + private boolean lightingDependsOnVertexPosition; + + static final int LIGHT_AMBIENT_R = 0; + static final int LIGHT_AMBIENT_G = 1; + static final int LIGHT_AMBIENT_B = 2; + static final int LIGHT_DIFFUSE_R = 3; + static final int LIGHT_DIFFUSE_G = 4; + static final int LIGHT_DIFFUSE_B = 5; + static final int LIGHT_SPECULAR_R = 6; + static final int LIGHT_SPECULAR_G = 7; + static final int LIGHT_SPECULAR_B = 8; + + static final int LIGHT_COLOR_COUNT = 9; + + // Used to shuttle lighting calcs around + // (no need to re-allocate all the time) + protected float[] tempLightingContribution = new float[LIGHT_COLOR_COUNT]; + protected float[] worldNormal = new float[4]; + + // Used in light_triangle(). Allocated here once to + // avoid re-allocating each time + protected float[] dv1 = new float[3]; + protected float[] dv2 = new float[3]; + protected float[] norm = new float[3]; + + // ........................................................ + + /** + * This is turned on at beginCamera, and off at endCamera + * Currently we don't support nested begin/end cameras. + * If we wanted to, this variable would have to become a stack. + */ + protected boolean manipulatingCamera; + + // These two matrices always point to either the modelview + // or the modelviewInv, but they are swapped during + // when in camera maniuplation mode. That way camera transforms + // are automatically accumulated in inverse on the modelview matrix. + protected PMatrix forwardTransform; + protected PMatrix reverseTransform; + + // Added by ewjordan for accurate texturing purposes. Screen plane is + // not scaled to pixel-size, so these manually keep track of its size + // from frustum() calls. Sorry to add public vars, is there a way + // to compute these from something publicly available without matrix ops? + // (used once per triangle in PTriangle with ENABLE_ACCURATE_TEXTURES) + protected float leftScreen; + protected float rightScreen; + protected float topScreen; + protected float bottomScreen; + protected float nearPlane; //depth of near clipping plane + + // ........................................................ + + // pos of first vertex of current shape in vertices array + protected int vertex_start; + + // i think vertex_end is actually the last vertex in the current shape + // and is separate from vertexCount for occasions where drawing happens + // on endDraw() with all the triangles being depth sorted + protected int vertex_end; + + // vertices may be added during clipping against the near plane. + protected int vertex_end_including_clip_verts; + + // used for sorting points when triangulating a polygon + // warning - maximum number of vertices for a polygon is DEFAULT_VERTICES + protected int vertex_order[] = new int[DEFAULT_VERTICES]; + + // ........................................................ + + protected int pathCount; + protected int pathOffset[] = new int[64]; + protected int pathLength[] = new int[64]; + + // ........................................................ + + // lines + static final int DEFAULT_LINES = 512; + public PLine line; // used for drawing + protected int lines[][] = new int[DEFAULT_LINES][LINE_FIELD_COUNT]; + protected int lineCount; + + // ........................................................ + + // triangles + static final int DEFAULT_TRIANGLES = 256; + public PTriangle triangle; + protected int triangles[][] = + new int[DEFAULT_TRIANGLES][TRIANGLE_FIELD_COUNT]; + protected float triangleColors[][][] = + new float[DEFAULT_TRIANGLES][3][TRIANGLE_COLOR_COUNT]; + protected int triangleCount; // total number of triangles + + // cheap picking someday + public int shape_index; + + // ........................................................ + + /** + * Sets whether texture coordinates passed to + * vertex() calls will be based on coordinates that are + * based on the IMAGE or NORMALIZED. + */ + //public int textureMode; + + /** + * Current horizontal coordinate for texture, will always + * be between 0 and 1, even if using textureMode(IMAGE). + */ + //public float textureU; + + /** Current vertical coordinate for texture, see above. */ + //public float textureV; + + //public PImage textureImage; + + static final int DEFAULT_TEXTURES = 3; + protected PImage textures[] = new PImage[DEFAULT_TEXTURES]; + int texture_index; + + // ........................................................ + + /** + * Normals + */ + //public float normalX, normalY, normalZ; + //public int normalMode; + //public int normalCount; + + // ........................................................ + + // [toxi031031] new & faster sphere code w/ support flexibile resolutions + // will be set by sphereDetail() or 1st call to sphere() + float sphereX[], sphereY[], sphereZ[]; + //public int sphereDetail = 0; + + // ........................................................ + + + /** + * Constructor for the PGraphics3 object. + * This prototype only exists because of annoying + * java compilers, and should not be used. + */ + /* + public PGraphics3D() { + forwardTransform = modelview; + reverseTransform = modelviewInv; + } + */ + + /* + public PGraphics3D(int iwidth, int iheight) { + this(iwidth, iheight, null); + } + */ + + + /** + * Constructor for the PGraphics3 object. Use this to ensure that + * the defaults get set properly. In a subclass, use this(w, h) + * as the first line of a subclass' constructor to properly set + * the internal fields and defaults. + * + * @param iwidth viewport width + * @param iheight viewport height + */ + public PGraphics3D(int iwidth, int iheight, PApplet parent) { + // super will add the listeners to the applet, and call resize() + super(iwidth, iheight, parent); + forwardTransform = modelview; + reverseTransform = modelviewInv; + //resize(iwidth, iheight); + //projection = new PMatrix(); + } + + + /** + * Called in repsonse to a resize event, handles setting the + * new width and height internally, as well as re-allocating + * the pixel buffer for the new size. + * + * Note that this will nuke any cameraMode() settings. + */ + public void resize(int iwidth, int iheight) { // ignore + insideDrawWait(); + insideResize = true; + + width = iwidth; + height = iheight; + width1 = width - 1; + height1 = height - 1; + + allocate(); + + // clear the screen with the old background color + //background(backgroundColor); + + // init perspective projection based on new dimensions + cameraFOV = 60 * DEG_TO_RAD; // at least for now + cameraX = width / 2.0f; + cameraY = height / 2.0f; + //cameraZ = cameraY / ((float) tan(PI * cameraFOV / 360f)); + cameraZ = cameraY / ((float) tan(cameraFOV / 2.0f)); + cameraNear = cameraZ / 10.0f; + cameraFar = cameraZ * 10.0f; + + cameraAspect = (float)width / (float)height; + + // init lights (in resize() instead of allocate() b/c needed by opengl) + lightType = new int[MAX_LIGHTS]; + lightPosition = new float[MAX_LIGHTS][3]; + lightDiffuse = new float[MAX_LIGHTS][3]; + lightNormal = new float[MAX_LIGHTS][3]; + lightSpecular = new float[MAX_LIGHTS][3]; + lightFalloffConstant = new float[MAX_LIGHTS]; + lightFalloffLinear = new float[MAX_LIGHTS]; + lightFalloffQuadratic = new float[MAX_LIGHTS]; + lightSpotAngle = new float[MAX_LIGHTS]; + lightSpotAngleCos = new float[MAX_LIGHTS]; + lightSpotConcentration = new float[MAX_LIGHTS]; + currentLightSpecular = new float[3]; + + // reset the cameraMode if PERSPECTIVE or ORTHOGRAPHIC + // will just be ignored if CUSTOM, the user's hosed anyways + //System.out.println("setting cameraMode to " + cameraMode); + //if (this.cameraMode != CUSTOM) cameraMode(this.cameraMode); + + // making this again here because things are weird + projection = new PMatrix(); + + modelview = new PMatrix(MATRIX_STACK_DEPTH); + modelviewInv = new PMatrix(MATRIX_STACK_DEPTH); + forwardTransform = modelview; + reverseTransform = modelviewInv; + + camera = new PMatrix(); + cameraInv = new PMatrix(); + + // set up the default camera + camera(); + + // defaults to perspective, if the user has setup up their + // own projection, they'll need to fix it after resize anyway. + // this helps the people who haven't set up their own projection. + perspective(); + + insideResize = false; // ok to draw again + } + + + protected void allocate() { + //System.out.println(this + " allocating for " + width + " " + height); + //new Exception().printStackTrace(); + + pixelCount = width * height; + pixels = new int[pixelCount]; + zbuffer = new float[pixelCount]; + + if (mainDrawingSurface) { + // because of a java 1.1 bug, pixels must be registered as + // opaque before their first run, the memimgsrc will flicker + // and run very slowly. + //backgroundColor |= 0xff000000; // just for good measure + for (int i = 0; i < pixelCount; i++) pixels[i] = backgroundColor; + //for (int i = 0; i < pixelCount; i++) pixels[i] = 0xffffffff; + + cm = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff);; + mis = new MemoryImageSource(width, height, pixels, 0, width); + mis.setFullBufferUpdates(true); + mis.setAnimated(true); + image = Toolkit.getDefaultToolkit().createImage(mis); + + } else { + // when not the main drawing surface, need to set the zbuffer, + // because there's a possibility that background() will not be called + for (int i = 0; i < pixelCount; i++) { + zbuffer[i] = Float.MAX_VALUE; + } + } + + stencil = new int[pixelCount]; + + line = new PLine(this); + triangle = new PTriangle(this); + + // can't un-set this because this may be only a resize (Bug #463) + //defaultsInited = false; + //System.out.println(this + " done allocating"); + } + + + public void beginDraw() { + insideResizeWait(); + insideDraw = true; + + // need to call defaults(), but can only be done when it's ok + // to draw (i.e. for opengl, no drawing can be done outside + // beginDraw/endDraw). + if (!defaultsInited) defaults(); + + resetMatrix(); // reset model matrix + + // reset vertices + vertexCount = 0; + + modelview.set(camera); + modelviewInv.set(cameraInv); + + // clear out the lights, they'll have to be turned on again + lightCount = 0; + lightingDependsOnVertexPosition = false; + lightFalloff(1, 0, 0); + lightSpecular(0, 0, 0); + + // reset lines + lineCount = 0; + if (line != null) line.reset(); // is this necessary? + pathCount = 0; + + // reset triangles + triangleCount = 0; + if (triangle != null) triangle.reset(); // necessary? + + vertex_start = 0; + //vertex_end = 0; + + // reset textures + texture_index = 0; + + normal(0, 0, 1); + } + + + /** + * See notes in PGraphics. + * If z-sorting has been turned on, then the triangles will + * all be quicksorted here (to make alpha work more properly) + * and then blit to the screen. + */ + public void endDraw() { + // no need to z order and render + // shapes were already rendered in endShape(); + // (but can't return, since needs to update memimgsrc) + if (hints[ENABLE_DEPTH_SORT]) { + flush(); + } + // blit to screen + // moving this back here (post-68) because of macosx thread problem + if (mis != null) { + mis.newPixels(pixels, cm, 0, width); + } + // mark pixels as having been updated, so that they'll work properly + // when this PGraphics is drawn using image(). + updatePixels(); + + //System.out.println(this + " end draw"); + insideDraw = false; + } + + + /** + * Emit any sorted geometry that's been collected on this frame. + */ + protected void flush() { + if (triangleCount > 0) { + if (hints[ENABLE_DEPTH_SORT]) { + depth_sort_triangles(); + } + render_triangles(); + } + if (lineCount > 0) { + if (hints[ENABLE_DEPTH_SORT]) { + depth_sort_lines(); + } + render_lines(); + } + } + + + public void defaults() { + super.defaults(); + + manipulatingCamera = false; + forwardTransform = modelview; + reverseTransform = modelviewInv; + + perspective(); + + // easiest for beginners + textureMode(IMAGE); + + emissive(0.0f); + specular(0.5f); + shininess(1.0f); + } + + + ////////////////////////////////////////////////////////////// + + + public void beginShape(int kind) { + shape = kind; + + shape_index = shape_index + 1; + if (shape_index == -1) { + shape_index = 0; + } + + if (hints[ENABLE_DEPTH_SORT]) { + // continue with previous vertex, line and triangle count + // all shapes are rendered at endDraw(); + vertex_start = vertexCount; + vertex_end = 0; + + } else { + // reset vertex, line and triangle information + // every shape is rendered at endShape(); + vertexCount = 0; + if (line != null) line.reset(); // necessary? + lineCount = 0; + pathCount = 0; + if (triangle != null) triangle.reset(); // necessary? + triangleCount = 0; + } + textureImage = null; + + splineVertexCount = 0; + normalMode = AUTO_NORMAL; + normalCount = 0; + } + + + /** + * Sets the current normal vector. + *

+ * This is for drawing three dimensional shapes and surfaces, + * allowing you to specify a vector perpendicular to the surface + * of the shape, which determines how lighting affects it. + *

+ * For the most part, PGraphics3D will attempt to automatically + * assign normals to shapes, but since that's imperfect, + * this is a better option when you want more control. + *

+ * For people familiar with OpenGL, this function is basically + * identical to glNormal3f(). + *

+ * Only applies inside a beginShape/endShape block. + */ + public void normal(float nx, float ny, float nz) { + normalX = nx; + normalY = ny; + normalZ = nz; + + // if drawing a shape and the normal hasn't been set yet, + // then we need to set the normals for each vertex so far + if (shape != 0) { + if (normalCount == 0) { + for (int i = vertex_start; i < vertexCount; i++) { + vertices[i][NX] = normalX; + vertices[i][NY] = normalY; + vertices[i][NZ] = normalZ; + } + } + + normalCount++; + if (normalCount == 1) { + // One normal per begin/end shape + normalMode = MANUAL_SHAPE_NORMAL; + } else { + // a separate normal for each vertex + normalMode = MANUAL_VERTEX_NORMAL; + } + } + } + + + public void texture(PImage image) { + textureImage = image; + + if (texture_index == textures.length - 1) { + PImage temp[] = new PImage[texture_index<<1]; + System.arraycopy(textures, 0, temp, 0, texture_index); + textures = temp; + //message(CHATTER, "allocating more textures " + textures.length); + } + if (textures[texture_index] != null) { + texture_index++; + } + textures[texture_index] = image; + } + + + public void vertex(float x, float y) { + setup_vertex(x, y, 0); + } + + + public void vertex(float x, float y, float u, float v) { + textureVertex(u, v); + setup_vertex(x, y, 0); + } + + + public void vertex(float x, float y, float z) { + setup_vertex(x, y, z); + } + + + public void vertex(float x, float y, float z, + float u, float v) { + textureVertex(u, v); + setup_vertex(x, y, z); + } + + + protected void setup_vertex(float x, float y, float z) { + if (vertexCount == vertices.length) { + float temp[][] = new float[vertexCount << 1][VERTEX_FIELD_COUNT]; + System.arraycopy(vertices, 0, temp, 0, vertexCount); + vertices = temp; + int temp2[] = new int[vertexCount << 1]; + System.arraycopy(vertex_order, 0, temp2, 0, vertexCount); + vertex_order = temp2; + //System.out.println("allocating more vertices " + vertices.length); + } + float vertex[] = vertices[vertexCount]; + + // only do this if we're using an irregular (POLYGON) shape that + // will go through the triangulator. otherwise it'll do thinks like + // disappear in mathematically odd ways + // http://dev.processing.org/bugs/show_bug.cgi?id=444 + if (shape == POLYGON) { + if (vertexCount > 0) { + float pvertex[] = vertices[vertexCount-1]; + if ((abs(pvertex[MX] - x) < EPSILON) && + (abs(pvertex[MY] - y) < EPSILON) && + (abs(pvertex[MZ] - z) < EPSILON)) { + // this vertex is identical, don't add it, + // because it will anger the triangulator + return; + } + } + } + + // user called vertex(), so that invalidates anything queued + // up for curve vertices. if this is internally called by + // spline_segment, then splineVertexCount will be saved and restored. + splineVertexCount = 0; + + vertex[MX] = x; + vertex[MY] = y; + vertex[MZ] = z; + + if (fill) { + vertex[R] = fillR; + vertex[G] = fillG; + vertex[B] = fillB; + vertex[A] = fillA; + + vertex[AR] = ambientR; + vertex[AG] = ambientG; + vertex[AB] = ambientB; + + vertex[SPR] = specularR; + vertex[SPG] = specularG; + vertex[SPB] = specularB; + vertex[SPA] = specularA; + + vertex[SHINE] = shininess; + + vertex[ER] = emissiveR; + vertex[EG] = emissiveG; + vertex[EB] = emissiveB; + } + + if (stroke) { + vertex[SR] = strokeR; + vertex[SG] = strokeG; + vertex[SB] = strokeB; + vertex[SA] = strokeA; + vertex[SW] = strokeWeight; + } + + if (textureImage != null) { + vertex[U] = textureU; + vertex[V] = textureV; + } + + vertex[NX] = normalX; + vertex[NY] = normalY; + vertex[NZ] = normalZ; + + vertex[BEEN_LIT] = 0; + + vertexCount++; + } + + + /** + * See notes with the bezier() function. + */ + public void bezierVertex(float x2, float y2, + float x3, float y3, + float x4, float y4) { + bezierVertex(x2, y2, 0, x3, y3, 0, x4, y4, 0); + } + + + /** + * See notes with the bezier() function. + */ + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + if (splineVertexCount > 0) { + float vertex[] = splineVertices[splineVertexCount-1]; + splineVertex(vertex[MX], vertex[MY], vertex[MZ], true); + + } else if (vertexCount > 0) { + // make sure there's at least a call to vertex() + float vertex[] = vertices[vertexCount-1]; + splineVertex(vertex[MX], vertex[MY], vertex[MZ], true); + + } else { + throw new RuntimeException("A call to vertex() must be used " + + "before bezierVertex()"); + } + splineVertex(x2, y2, z2, true); + splineVertex(x3, y3, z3, true); + splineVertex(x4, y4, z4, true); + } + + + public void endShape(int mode) { + vertex_end = vertexCount; + vertex_end_including_clip_verts = vertex_end; + + // don't try to draw if there are no vertices + // (fixes a bug in LINE_LOOP that re-adds a nonexistent vertex) + if (vertexCount == 0) { + shape = 0; + return; + } + + + // ------------------------------------------------------------------ + // 2D or 3D POINTS FROM MODEL (MX, MY, MZ) TO CAMERA SPACE (VX, VY, VZ) + // It is necessary to do this now because we will be clipping them on + // add_triangle. + + for (int i = vertex_start; i < vertex_end; i++) { + float vertex[] = vertices[i]; + + vertex[VX] = + modelview.m00*vertex[MX] + modelview.m01*vertex[MY] + + modelview.m02*vertex[MZ] + modelview.m03; + vertex[VY] = + modelview.m10*vertex[MX] + modelview.m11*vertex[MY] + + modelview.m12*vertex[MZ] + modelview.m13; + vertex[VZ] = + modelview.m20*vertex[MX] + modelview.m21*vertex[MY] + + modelview.m22*vertex[MZ] + modelview.m23; + vertex[VW] = + modelview.m30*vertex[MX] + modelview.m31*vertex[MY] + + modelview.m32*vertex[MZ] + modelview.m33; + + // normalize + if (vertex[VW] != 0 && vertex[VW] != 1) { + vertex[VX] /= vertex[VW]; + vertex[VY] /= vertex[VW]; + vertex[VZ] /= vertex[VW]; + } + vertex[VW] = 1; + } + + // ------------------------------------------------------------------ + // CREATE LINES + + int increment = 1; + int stop = 0; + //int counter = 0; + + if (stroke) { + switch (shape) { + + case POINTS: + { + stop = vertex_end; + for (int i = vertex_start; i < stop; i++) { + add_path(); // total overkill for points + add_line(i, i); + } + } + break; + + case LINES: + //case LINE_STRIP: + //case LINE_LOOP: + { + // store index of first vertex + int first = lineCount; + stop = vertex_end - 1; + increment = (shape == LINES) ? 2 : 1; + + // for LINE_STRIP and LINE_LOOP, make this all one path + if (shape != LINES) add_path(); + + for (int i = vertex_start; i < stop; i+=increment) { + // for LINES, make a new path for each segment + if (shape == LINES) add_path(); + add_line(i, i+1); + } + + // for LINE_LOOP, close the loop with a final segment + //if (shape == LINE_LOOP) { + if (mode == CLOSE) { + add_line(stop, lines[first][VERTEX1]); + } + } + break; + + case TRIANGLES: + { + for (int i = vertex_start; i < vertex_end-2; i += 3) { + add_path(); + //counter = i - vertex_start; + add_line(i+0, i+1); + add_line(i+1, i+2); + add_line(i+2, i+0); + } + } + break; + + case TRIANGLE_STRIP: + { + // first draw all vertices as a line strip + stop = vertex_end-1; + + add_path(); + for (int i = vertex_start; i < stop; i++) { + //counter = i - vertex_start; + add_line(i,i+1); + } + + // then draw from vertex (n) to (n+2) + stop = vertex_end-2; + for (int i = vertex_start; i < stop; i++) { + add_path(); + add_line(i,i+2); + } + } + break; + + case TRIANGLE_FAN: + { + // this just draws a series of line segments + // from the center to each exterior point + for (int i = vertex_start + 1; i < vertex_end; i++) { + add_path(); + add_line(vertex_start, i); + } + + // then a single line loop around the outside. + add_path(); + for (int i = vertex_start + 1; i < vertex_end-1; i++) { + add_line(i, i+1); + } + // closing the loop + add_line(vertex_end-1, vertex_start + 1); + } + break; + + case QUADS: + { + for (int i = vertex_start; i < vertex_end; i += 4) { + add_path(); + //counter = i - vertex_start; + add_line(i+0, i+1); + add_line(i+1, i+2); + add_line(i+2, i+3); + add_line(i+3, i+0); + } + } + break; + + case QUAD_STRIP: + { + for (int i = vertex_start; i < vertex_end - 3; i += 2) { + add_path(); + add_line(i+0, i+2); + add_line(i+2, i+3); + add_line(i+3, i+1); + add_line(i+1, i+0); + } + /* + // first draw all vertices as a line strip + stop = vertex_end - 1; + + add_path(); + for (int i = vertex_start; i < stop; i++) { + counter = i - vertex_start; + add_line(i, i+1); + } + + // then draw from vertex (n) to (n+3) + stop = vertex_end-2; + increment = 2; + + add_path(); + for (int i = vertex_start; i < stop; i += increment) { + add_line(i, i+3); + } + */ + } + break; + + case POLYGON: + //case CONCAVE_POLYGON: + //case CONVEX_POLYGON: + { + // store index of first vertex + //int first = lineCount; + stop = vertex_end - 1; + + add_path(); + for (int i = vertex_start; i < stop; i++) { + add_line(i, i+1); + //System.out.println("adding line " + i); + } + if (mode == CLOSE) { + // draw the last line connecting back to the first point in poly + add_line(stop, vertex_start); //lines[first][VERTEX1]); + } + } + break; + } + } + + // ------------------------------------------------------------------ + // CREATE TRIANGLES + + if (fill) { + switch (shape) { + case TRIANGLE_FAN: + { + stop = vertex_end - 1; + for (int i = vertex_start + 1; i < stop; i++) { + add_triangle(vertex_start, i, i+1); + } + } + break; + + case TRIANGLES: + case TRIANGLE_STRIP: + { + stop = vertex_end - 2; + increment = (shape == TRIANGLES) ? 3 : 1; + for (int i = vertex_start; i < stop; i += increment) { + // have to switch between clockwise/counter-clockwise + // otherwise the feller is backwards and renderer won't draw + if ((i % 2) == 0) { + add_triangle(i, i+2, i+1); + } else { + add_triangle(i, i+1, i+2); + } + } + } + break; + + case QUADS: + { + stop = vertexCount-3; + for (int i = vertex_start; i < stop; i += 4) { + // first triangle + add_triangle(i, i+1, i+2); + // second triangle + add_triangle(i, i+2, i+3); + } + } + break; + + case QUAD_STRIP: + { + stop = vertexCount-3; + for (int i = vertex_start; i < stop; i += 2) { + // first triangle + add_triangle(i+0, i+2, i+1); + // second triangle + add_triangle(i+2, i+3, i+1); + } + } + break; + + case POLYGON: + //case CONCAVE_POLYGON: + //case CONVEX_POLYGON: + { + triangulate_polygon(); + } + break; + } + } + + + // ------------------------------------------------------------------ + // TRANSFORM / LIGHT / CLIP + + if (lightCount > 0 && fill) { + handle_lighting(); + } + else { + handle_no_lighting(); + } + + + + // ------------------------------------------------------------------ + // POINTS FROM CAMERA SPACE (VX, VY, VZ) TO SCREEN SPACE (X, Y, Z) + // this appears to be wasted time with the opengl renderer + + for (int i = vertex_start; i < vertex_end_including_clip_verts; i++) { + float vx[] = vertices[i]; + + float ox = + projection.m00*vx[VX] + projection.m01*vx[VY] + + projection.m02*vx[VZ] + projection.m03*vx[VW]; + float oy = + projection.m10*vx[VX] + projection.m11*vx[VY] + + projection.m12*vx[VZ] + projection.m13*vx[VW]; + float oz = + projection.m20*vx[VX] + projection.m21*vx[VY] + + projection.m22*vx[VZ] + projection.m23*vx[VW]; + float ow = + projection.m30*vx[VX] + projection.m31*vx[VY] + + projection.m32*vx[VZ] + projection.m33*vx[VW]; + + if (ow != 0 && ow != 1) { + ox /= ow; oy /= ow; oz /= ow; + } + + vx[X] = width * (1 + ox) / 2.0f; + vx[Y] = height * (1 + oy) / 2.0f; + vx[Z] = (oz + 1) / 2.0f; + } + + + // ------------------------------------------------------------------ + // RENDER SHAPES FILLS HERE WHEN NOT DEPTH SORTING + + // if true, the shapes will be rendered on endDraw + if (!hints[ENABLE_DEPTH_SORT]) { + if (fill) render_triangles(); + if (stroke) render_lines(); + } + + shape = 0; + } + + + protected final void add_path() { + if (pathCount == pathOffset.length) { + int temp1[] = new int[pathCount << 1]; + System.arraycopy(pathOffset, 0, temp1, 0, pathCount); + pathOffset = temp1; + int temp2[] = new int[pathCount << 1]; + System.arraycopy(pathLength, 0, temp2, 0, pathCount); + pathLength = temp2; + } + pathOffset[pathCount] = lineCount; + pathLength[pathCount] = 0; + pathCount++; + } + + + protected void add_line(int a, int b) { + add_line_with_clip(a, b); + } + + protected final void add_line_with_clip(int a, int b) { + float az = vertices[a][VZ]; + float bz = vertices[b][VZ]; + if (az > cameraNear) { + if (bz > cameraNear) { + return; + } + int cb = interpolate_clip_vertex(a, b); + add_line_no_clip(cb, b); + return; + } + else { + if (bz <= cameraNear) { + add_line_no_clip(a, b); + return; + } + int cb = interpolate_clip_vertex(a, b); + add_line_no_clip(a, cb); + return; + } + } + + protected final void add_line_no_clip(int a, int b) { + if (lineCount == lines.length) { + int temp[][] = new int[lineCount<<1][LINE_FIELD_COUNT]; + System.arraycopy(lines, 0, temp, 0, lineCount); + lines = temp; + //message(CHATTER, "allocating more lines " + lines.length); + } + lines[lineCount][VERTEX1] = a; + lines[lineCount][VERTEX2] = b; + lines[lineCount][INDEX] = -1; + + lines[lineCount][STROKE_MODE] = strokeCap | strokeJoin; + lines[lineCount][STROKE_WEIGHT] = (int) (strokeWeight + 0.5f); // hmm + lineCount++; + + // mark this piece as being part of the current path + pathLength[pathCount-1]++; + } + + + protected void add_triangle(int a, int b, int c) { + add_triangle_with_clip(a, b, c); + //add_triangle_no_clip(a, b, c); + } + + protected final void add_triangle_with_clip(int a, int b, int c) { + boolean aClipped = false; + boolean bClipped = false; + //boolean cClipped = false; + int clippedCount = 0; + + cameraNear = -8; + if (vertices[a][VZ] > cameraNear) { + aClipped = true; + clippedCount++; + } + if (vertices[b][VZ] > cameraNear) { + bClipped = true; + clippedCount++; + } + if (vertices[c][VZ] > cameraNear) { + //cClipped = true; + clippedCount++; + } + if (clippedCount == 0) { + add_triangle_no_clip(a, b, c); + return; + } + else if (clippedCount == 3) { + return; + } + // | . + // In this case there is only one visible point. |/| + // So we'll have to make two new points on the clip line <| | + // and add that triangle instead. |\| + // | . + else if (clippedCount == 2) { + //System.out.println("Clipped two"); + + int ca, cb, cc, cd, ce; + if (!aClipped) { + ca = a; + cb = b; + cc = c; + } + else if (!bClipped) { + ca = b; + cb = a; + cc = c; + } + else { //if (!cClipped) { + ca = c; + cb = b; + cc = a; + } + + cd = interpolate_clip_vertex(ca, cb); + ce = interpolate_clip_vertex(ca, cc); + add_triangle_no_clip(ca, cd, ce); + return; + } + + // . | + // In this case there are two visible points. |\| + // So we'll have to make two new points on the clip line | |> + // and then add two new triangles. |/| + // . | + else { // (clippedCount == 1) { + //System.out.println("Clipped one"); + int ca, cb, cc, cd, ce; + if (aClipped) { + //System.out.println("aClipped"); + ca = c; + cb = b; + cc = a; + } + else if (bClipped) { + //System.out.println("bClipped"); + ca = a; + cb = c; + cc = b; + } + else { //if (cClipped) { + //System.out.println("cClipped"); + ca = a; + cb = b; + cc = c; + } + + cd = interpolate_clip_vertex(ca, cc); + ce = interpolate_clip_vertex(cb, cc); + add_triangle_no_clip(ca, cd, cb); + //System.out.println("ca: " + ca + ", " + vertices[ca][VX] + ", " + vertices[ca][VY] + ", " + vertices[ca][VZ]); + //System.out.println("cd: " + cd + ", " + vertices[cd][VX] + ", " + vertices[cd][VY] + ", " + vertices[cd][VZ]); + //System.out.println("cb: " + cb + ", " + vertices[cb][VX] + ", " + vertices[cb][VY] + ", " + vertices[cb][VZ]); + add_triangle_no_clip(cb, cd, ce); + return; + } + } + + private final int interpolate_clip_vertex(int a, int b) { + float[] va; + float[] vb; + // Set up va, vb such that va[VZ] >= vb[VZ] + if (vertices[a][VZ] < vertices[b][VZ]) { + va = vertices[b]; + vb = vertices[a]; + } + else { + va = vertices[a]; + vb = vertices[b]; + } + float az = va[VZ]; + float bz = vb[VZ]; + + float dz = az - bz; + // If they have the same z, just use pt. a. + if (dz == 0) { + return a; + } + //float pa = (az - cameraNear) / dz; + //float pb = (cameraNear - bz) / dz; + float pa = (cameraNear - bz) / dz; + float pb = 1 - pa; + + vertex(pa * va[MX] + pb * vb[MX], + pa * va[MY] + pb * vb[MY], + pa * va[MZ] + pb * vb[MZ]); + int irv = vertexCount - 1; + vertex_end_including_clip_verts++; + float[] rv = vertices[irv]; + + rv[X] = pa * va[X] + pb * vb[X]; + rv[Y] = pa * va[Y] + pb * vb[Y]; + rv[Z] = pa * va[Z] + pb * vb[Z]; + + rv[VX] = pa * va[VX] + pb * vb[VX]; + rv[VY] = pa * va[VY] + pb * vb[VY]; + rv[VZ] = pa * va[VZ] + pb * vb[VZ]; + rv[VW] = pa * va[VW] + pb * vb[VW]; + + rv[R] = pa * va[R] + pb * vb[R]; + rv[G] = pa * va[G] + pb * vb[G]; + rv[B] = pa * va[B] + pb * vb[B]; + rv[A] = pa * va[A] + pb * vb[A]; + + rv[U] = pa * va[U] + pb * vb[U]; + rv[V] = pa * va[V] + pb * vb[V]; + + rv[SR] = pa * va[SR] + pb * vb[SR]; + rv[SG] = pa * va[SG] + pb * vb[SG]; + rv[SB] = pa * va[SB] + pb * vb[SB]; + rv[SA] = pa * va[SA] + pb * vb[SA]; + + rv[NX] = pa * va[NX] + pb * vb[NX]; + rv[NY] = pa * va[NY] + pb * vb[NY]; + rv[NZ] = pa * va[NZ] + pb * vb[NZ]; + + rv[SW] = pa * va[SW] + pb * vb[SW]; + + rv[AR] = pa * va[AR] + pb * vb[AR]; + rv[AG] = pa * va[AG] + pb * vb[AG]; + rv[AB] = pa * va[AB] + pb * vb[AB]; + + rv[SPR] = pa * va[SPR] + pb * vb[SPR]; + rv[SPG] = pa * va[SPG] + pb * vb[SPG]; + rv[SPB] = pa * va[SPB] + pb * vb[SPB]; + rv[SPA] = pa * va[SPA] + pb * vb[SPA]; + + rv[ER] = pa * va[ER] + pb * vb[ER]; + rv[EG] = pa * va[EG] + pb * vb[EG]; + rv[EB] = pa * va[EB] + pb * vb[EB]; + + rv[SHINE] = pa * va[SHINE] + pb * vb[SHINE]; + + rv[BEEN_LIT] = 0; + + return irv; + } + + + protected final void add_triangle_no_clip(int a, int b, int c) { + //System.out.println("adding triangle " + triangleCount); + if (triangleCount == triangles.length) { + int temp[][] = new int[triangleCount<<1][TRIANGLE_FIELD_COUNT]; + System.arraycopy(triangles, 0, temp, 0, triangleCount); + triangles = temp; + //message(CHATTER, "allocating more triangles " + triangles.length); + float ftemp[][][] = new float[triangleCount<<1][3][TRIANGLE_COLOR_COUNT]; + System.arraycopy(triangleColors, 0, ftemp, 0, triangleCount); + triangleColors = ftemp; + } + triangles[triangleCount][VERTEX1] = a; + triangles[triangleCount][VERTEX2] = b; + triangles[triangleCount][VERTEX3] = c; + + if (textureImage == null) { + triangles[triangleCount][TEXTURE_INDEX] = -1; + } else { + triangles[triangleCount][TEXTURE_INDEX] = texture_index; + } + + triangles[triangleCount][INDEX] = shape_index; + triangleCount++; + } + + + protected void depth_sort_triangles() { + //System.out.println("sorting " + triangleCount + " triangles"); + depth_sort_triangles_internal(0, triangleCount-1); + } + + + protected void depth_sort_triangles_internal(int i, int j) { + int pivotIndex = (i+j)/2; + depth_sort_triangles_swap(pivotIndex, j); + int k = depth_sort_triangles_partition(i-1, j); + depth_sort_triangles_swap(k, j); + if ((k-i) > 1) depth_sort_triangles_internal(i, k-1); + if ((j-k) > 1) depth_sort_triangles_internal(k+1, j); + } + + + protected int depth_sort_triangles_partition(int left, int right) { + int pivot = right; + do { + while (depth_sort_triangles_compare(++left, pivot) < 0) { } + while ((right != 0) && + (depth_sort_triangles_compare(--right, pivot) > 0)) { } + depth_sort_triangles_swap(left, right); + } while (left < right); + depth_sort_triangles_swap(left, right); + return left; + } + + + protected void depth_sort_triangles_swap(int a, int b) { + int tempi[] = triangles[a]; + triangles[a] = triangles[b]; + triangles[b] = tempi; + float tempf[][] = triangleColors[a]; + triangleColors[a] = triangleColors[b]; + triangleColors[b] = tempf; + } + + + protected float depth_sort_triangles_compare(int a, int b) { + if (Float.isNaN(vertices[triangles[a][VERTEX1]][Z]) || + Float.isNaN(vertices[triangles[a][VERTEX2]][Z]) || + Float.isNaN(vertices[triangles[a][VERTEX3]][Z]) || + Float.isNaN(vertices[triangles[b][VERTEX1]][Z]) || + Float.isNaN(vertices[triangles[b][VERTEX2]][Z]) || + Float.isNaN(vertices[triangles[b][VERTEX3]][Z])) { + throw new RuntimeException("nan triangle"); + } + return + (vertices[triangles[b][VERTEX1]][Z] + + vertices[triangles[b][VERTEX2]][Z] + + vertices[triangles[b][VERTEX3]][Z]) - + (vertices[triangles[a][VERTEX1]][Z] + + vertices[triangles[a][VERTEX2]][Z] + + vertices[triangles[a][VERTEX3]][Z]); + } + + + protected void render_triangles() { + //System.out.println("rendering " + triangleCount + " triangles"); + + if (raw != null) { + raw.colorMode(RGB, 1); + raw.noStroke(); + raw.beginShape(TRIANGLES); + } + + for (int i = 0; i < triangleCount; i ++) { + float a[] = vertices[triangles[i][VERTEX1]]; + float b[] = vertices[triangles[i][VERTEX2]]; + float c[] = vertices[triangles[i][VERTEX3]]; + int tex = triangles[i][TEXTURE_INDEX]; + int index = triangles[i][INDEX]; + + // ewjordan: hack to 'fix' accuracy issues when drawing in 2d + // see also render_lines() where similar hack is employed + float shift = 0.15f;//was 0.49f + boolean shifted = false; + if (drawing2D() && (a[MZ] == 0)) { + shifted = true; + a[X] += shift; + a[Y] += shift; + a[VX] += shift*a[VW]; + a[VY] += shift*a[VW]; + b[X] += shift; + b[Y] += shift; + b[VX] += shift*b[VW]; + b[VY] += shift*b[VW]; + c[X] += shift; + c[Y] += shift; + c[VX] += shift*c[VW]; + c[VY] += shift*c[VW]; + } + + triangle.reset(); + + // This is only true when not textured. We really should pass SPECULAR + // straight through to triangle rendering. + float ar = min(1, triangleColors[i][0][TRI_DIFFUSE_R] + + triangleColors[i][0][TRI_SPECULAR_R]); + float ag = min(1, triangleColors[i][0][TRI_DIFFUSE_G] + + triangleColors[i][0][TRI_SPECULAR_G]); + float ab = min(1, triangleColors[i][0][TRI_DIFFUSE_B] + + triangleColors[i][0][TRI_SPECULAR_B]); + float br = min(1, triangleColors[i][1][TRI_DIFFUSE_R] + + triangleColors[i][1][TRI_SPECULAR_R]); + float bg = min(1, triangleColors[i][1][TRI_DIFFUSE_G] + + triangleColors[i][1][TRI_SPECULAR_G]); + float bb = min(1, triangleColors[i][1][TRI_DIFFUSE_B] + + triangleColors[i][1][TRI_SPECULAR_B]); + float cr = min(1, triangleColors[i][2][TRI_DIFFUSE_R] + + triangleColors[i][2][TRI_SPECULAR_R]); + float cg = min(1, triangleColors[i][2][TRI_DIFFUSE_G] + + triangleColors[i][2][TRI_SPECULAR_G]); + float cb = min(1, triangleColors[i][2][TRI_DIFFUSE_B] + + triangleColors[i][2][TRI_SPECULAR_B]); + + if (tex > -1 && textures[tex] != null) { + triangle.setTexture(textures[tex]); + triangle.setUV(a[U], a[V], b[U], b[V], c[U], c[V]); + } + + triangle.setIntensities(ar, ag, ab, a[A], + br, bg, bb, b[A], + cr, cg, cb, c[A]); + + triangle.setVertices(a[X], a[Y], a[Z], + b[X], b[Y], b[Z], + c[X], c[Y], c[Z]); + + // Need to pass camera-space coordinates to triangle renderer + // in order to compute true texture coordinates, else skip it + if (hints[ENABLE_ACCURATE_TEXTURES]){ + triangle.setCamVertices(a[VX], a[VY], a[VZ], + b[VX], b[VY], b[VZ], + c[VX], c[VY], c[VZ]); + } + + triangle.setIndex(index); + triangle.render(); + + //System.out.println(i + " " + a[Z] + " " + b[Z] + " " + c[Z]); + + if (raw != null) { + if (raw instanceof PGraphics3D) { + if ((a[VW] != 0) && (b[VW] != 0) && (c[VW] != 0)) { + raw.fill(ar, ag, ab, a[A]); + raw.vertex(a[VX] / a[VW], a[VY] / a[VW], a[VZ] / a[VW]); + raw.fill(br, bg, bb, b[A]); + raw.vertex(b[VX] / b[VW], b[VY] / b[VW], b[VZ] / b[VW]); + raw.fill(cr, cg, cb, c[A]); + raw.vertex(c[VX] / c[VW], c[VY] / c[VW], c[VZ] / c[VW]); + } + } else { + raw.fill(ar, ag, ab, a[A]); + raw.vertex(a[X], a[Y]); + raw.fill(br, bg, bb, b[A]); + raw.vertex(b[X], b[Y]); + raw.fill(cr, cg, cb, c[A]); + raw.vertex(c[X], c[Y]); + } + } + + if (drawing2D() && shifted){ + a[X] -= shift; + a[Y] -= shift; + a[VX] -= shift*a[VW]; + a[VY] -= shift*a[VW]; + b[X] -= shift; + b[Y] -= shift; + b[VX] -= shift*b[VW]; + b[VY] -= shift*b[VW]; + c[X] -= shift; + c[Y] -= shift; + c[VX] -= shift*c[VW]; + c[VY] -= shift*c[VW]; + } + } + + if (raw != null) { + raw.endShape(); + } + } + + + protected void depth_sort_lines() { + } + + + protected void render_lines() { + if (raw != null) { + raw.colorMode(RGB, 1); + raw.noFill(); + raw.beginShape(LINES); + } + + for (int i = 0; i < lineCount; i ++) { + float a[] = vertices[lines[i][VERTEX1]]; + float b[] = vertices[lines[i][VERTEX2]]; + int index = lines[i][INDEX]; + + // 2D hack added by ewjordan 6/13/07 + // Offset coordinates by a little bit if drawing 2D graphics. + // http://dev.processing.org/bugs/show_bug.cgi?id=95 + + // This hack fixes a bug caused by numerical precision issues when + // applying the 3D transformations to coordinates in the screen plane + // that should actually not be altered under said transformations. + // It will not be applied if any transformations other than translations + // are active, nor should it apply in OpenGL mode (PGraphicsOpenGL + // overrides render_lines(), so this should be fine). + // This fix exposes a last-pixel bug in the lineClipCode() function + // of PLine.java, so that fix must remain in place if this one is used. + + // Note: the "true" fix for this bug is to change the pixel coverage + // model so that the threshold for display does not lie on an integer + // boundary. Search "diamond exit rule" for info the OpenGL approach. + + if (drawing2D() && a[MZ] == 0) { + a[X] += 0.01; + a[Y] += 0.01; + a[VX] += 0.01*a[VW]; + a[VY] += 0.01*a[VW]; + b[X] += 0.01; + b[Y] += 0.01; + b[VX] += 0.01*b[VW]; + b[VY] += 0.01*b[VW]; + } + // end 2d-hack + + line.reset(); + + line.setIntensities(a[SR], a[SG], a[SB], a[SA], + b[SR], b[SG], b[SB], b[SA]); + + line.setVertices(a[X], a[Y], a[Z], + b[X], b[Y], b[Z]); + + if (raw != null) { + if (raw instanceof PGraphics3D) { + if ((a[VW] != 0) && (b[VW] != 0)) { + raw.stroke(a[SR], a[SG], a[SB], a[SA]); + raw.vertex(a[VX] / a[VW], a[VY] / a[VW], a[VZ] / a[VW]); + raw.stroke(b[SR], b[SG], b[SB], b[SA]); + raw.vertex(b[VX] / b[VW], b[VY] / b[VW], b[VZ] / b[VW]); + } + } else { + raw.stroke(a[SR], a[SG], a[SB], a[SA]); + raw.vertex(a[X], a[Y]); + raw.stroke(b[SR], b[SG], b[SB], b[SA]); + raw.vertex(b[X], b[Y]); + } + } + + /* Seems okay to remove this because these vertices are not used again, but if problems arise, this needs to be uncommented + because the above change is destructive and may need to be undone before proceeding. + if (drawing2D() && a[MZ] == 0) { + a[X] -= 0.01; + a[Y] -= 0.01; + a[VX] -= 0.01*a[VW]; + a[VY] -= 0.01*a[VW]; + b[X] -= 0.01; + b[Y] -= 0.01; + b[VX] -= 0.01*b[VW]; + b[VY] -= 0.01*b[VW]; + }*/ + + line.setIndex(index); + line.draw(); + } + + if (raw != null) { + raw.endShape(); + } + } + + + /** + * Triangulate the current polygon. + *

+ * Simple ear clipping polygon triangulation adapted from code by + * John W. Ratcliff (jratcliff at verant.com). Presumably + * this + * bit of code from the web. + */ + private void triangulate_polygon() { + // this clipping algorithm only works in 2D, so in cases where a + // polygon is drawn perpendicular to the z-axis, the area will be zero, + // and triangulation will fail. as such, when the area calculates to + // zero, figure out whether x or y is empty, and calculate based on the + // two dimensions that actually contain information. + // http://dev.processing.org/bugs/show_bug.cgi?id=111 + int d1 = MX; + int d2 = MY; + // this brings up the nastier point that there may be cases where + // a polygon is irregular in space and will throw off the + // clockwise/counterclockwise calculation. for instance, if clockwise + // relative to x and z, but counter relative to y and z or something + // like that.. will wait to see if this is in fact a problem before + // hurting my head on the math. + + // first we check if the polygon goes clockwise or counterclockwise + float area = 0; + for (int p = vertex_end - 1, q = vertex_start; q < vertex_end; p = q++) { + area += (vertices[q][d1] * vertices[p][d2] - + vertices[p][d1] * vertices[q][d2]); + } + // rather than checking for the perpendicular case first, only do it + // when the area calculates to zero. checking for perpendicular would be + // a needless waste of time for the 99% case. + if (area == 0) { + // figure out which dimension is the perpendicular axis + boolean foundValidX = false; + boolean foundValidY = false; + + for (int i = vertex_start; i < vertex_end; i++) { + for (int j = i; j < vertex_end; j++){ + if ( vertices[i][MX] != vertices[j][MX] ) foundValidX = true; + if ( vertices[i][MY] != vertices[j][MY] ) foundValidY = true; + } + } + + if (foundValidX) { + //d1 = MX; // already the case + d2 = MZ; + } else if (foundValidY) { + // ermm.. which is the proper order for cw/ccw here? + d1 = MY; + d2 = MZ; + } else { + // screw it, this polygon is just f-ed up + return; + } + + // re-calculate the area, with what should be good values + for (int p = vertex_end - 1, q = vertex_start; q < vertex_end; p = q++) { + area += (vertices[q][d1] * vertices[p][d2] - + vertices[p][d1] * vertices[q][d2]); + } + } + + // don't allow polygons to come back and meet themselves, + // otherwise it will anger the triangulator + // http://dev.processing.org/bugs/show_bug.cgi?id=97 + float vfirst[] = vertices[vertex_start]; + float vlast[] = vertices[vertex_end-1]; + if ((abs(vfirst[MX] - vlast[MX]) < EPSILON) && + (abs(vfirst[MY] - vlast[MY]) < EPSILON) && + (abs(vfirst[MZ] - vlast[MZ]) < EPSILON)) { + vertex_end--; + } + + // then sort the vertices so they are always in a counterclockwise order + int j = 0; + if (area > 0) { + for (int i = vertex_start; i < vertex_end; i++) { + j = i - vertex_start; + vertex_order[j] = i; + } + } else { + for (int i = vertex_start; i < vertex_end; i++) { + j = i - vertex_start; + vertex_order[j] = (vertex_end - 1) - j; + } + } + + // remove vc-2 Vertices, creating 1 triangle every time + int vc = vertex_end - vertex_start; + int count = 2*vc; // complex polygon detection + + for (int m = 0, v = vc - 1; vc > 2; ) { + boolean snip = true; + + // if we start over again, is a complex polygon + if (0 >= (count--)) { + break; // triangulation failed + } + + // get 3 consecutive vertices + int u = v ; if (vc <= u) u = 0; // previous + v = u + 1; if (vc <= v) v = 0; // current + int w = v + 1; if (vc <= w) w = 0; // next + + // triangle A B C + float Ax = -vertices[vertex_order[u]][d1]; + float Ay = vertices[vertex_order[u]][d2]; + float Bx = -vertices[vertex_order[v]][d1]; + float By = vertices[vertex_order[v]][d2]; + float Cx = -vertices[vertex_order[w]][d1]; + float Cy = vertices[vertex_order[w]][d2]; + + // first we check if continues going ccw + if (EPSILON > (((Bx-Ax) * (Cy-Ay)) - ((By-Ay) * (Cx-Ax)))) { + continue; + } + + for (int p = 0; p < vc; p++) { + //float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; + //float cCROSSap, bCROSScp, aCROSSbp; + + if( (p == u) || (p == v) || (p == w) ) { + continue; + } + + float Px = -vertices[vertex_order[p]][d1]; + float Py = vertices[vertex_order[p]][d2]; + + float ax = Cx - Bx; float ay = Cy - By; + float bx = Ax - Cx; float by = Ay - Cy; + float cx = Bx - Ax; float cy = By - Ay; + float apx = Px - Ax; float apy = Py - Ay; + float bpx = Px - Bx; float bpy = Py - By; + float cpx = Px - Cx; float cpy = Py - Cy; + + float aCROSSbp = ax * bpy - ay * bpx; + float cCROSSap = cx * apy - cy * apx; + float bCROSScp = bx * cpy - by * cpx; + + if ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)) { + snip = false; + } + } + + if (snip) { + add_triangle(vertex_order[u], vertex_order[v], vertex_order[w]); + + m++; + + // remove v from remaining polygon + for (int s = v, t = v + 1; t < vc; s++, t++) { + vertex_order[s] = vertex_order[t]; + } + vc--; + + // reset error detection counter + count = 2 * vc; + } + } + } + + + private void toWorldNormal(float nx, float ny, float nz, float[] out) { + out[0] = + modelviewInv.m00*nx + modelviewInv.m10*ny + + modelviewInv.m20*nz + modelviewInv.m30; + out[1] = + modelviewInv.m01*nx + modelviewInv.m11*ny + + modelviewInv.m21*nz + modelviewInv.m31; + out[2] = + modelviewInv.m02*nx + modelviewInv.m12*ny + + modelviewInv.m22*nz + modelviewInv.m32; + out[3] = + modelviewInv.m03*nx + modelviewInv.m13*ny + + modelviewInv.m23*nz + modelviewInv.m33; + + if (out[3] != 0 && out[3] != 1) { + // divide by perspective coordinate + out[0] /= out[3]; out[1] /= out[3]; out[2] /= out[3]; + } + out[3] = 1; + + float nlen = mag(out[0], out[1], out[2]); // normalize + if (nlen != 0 && nlen != 1) { + out[0] /= nlen; out[1] /= nlen; out[2] /= nlen; + } + } + + + private void calc_lighting_contribution(int vIndex, + float[] contribution) { + calc_lighting_contribution(vIndex, contribution, false); + } + + private void calc_lighting_contribution(int vIndex, + float[] contribution, + boolean normalIsWorld) { + float[] v = vertices[vIndex]; + + float sr = v[SPR]; + float sg = v[SPG]; + float sb = v[SPB]; + + float wx = v[VX]; + float wy = v[VY]; + float wz = v[VZ]; + float shine = v[SHINE]; + + float nx; + float ny; + float nz; + if (!normalIsWorld) { + toWorldNormal(v[NX], v[NY], v[NZ], worldNormal); + nx = worldNormal[X]; + ny = worldNormal[Y]; + nz = worldNormal[Z]; + } + else { + nx = v[NX]; + ny = v[NY]; + nz = v[NZ]; + } + + + // Since the camera space == world space, + // we can test for visibility by the dot product of + // the normal with the direction from pt. to eye. + float dir = dot(nx, ny, nz, -wx, -wy, -wz); + // If normal is away from camera, choose its opposite. + // If we add backface culling, this will be backfacing + // (but since this is per vertex, it's more complicated) + if (dir < 0) { + nx = -nx; + ny = -ny; + nz = -nz; + } + + // These two terms will sum the contributions from the various lights + contribution[LIGHT_AMBIENT_R] = 0; + contribution[LIGHT_AMBIENT_G] = 0; + contribution[LIGHT_AMBIENT_B] = 0; + + contribution[LIGHT_DIFFUSE_R] = 0; + contribution[LIGHT_DIFFUSE_G] = 0; + contribution[LIGHT_DIFFUSE_B] = 0; + + contribution[LIGHT_SPECULAR_R] = 0; + contribution[LIGHT_SPECULAR_G] = 0; + contribution[LIGHT_SPECULAR_B] = 0; + + // for (int i = 0; i < MAX_LIGHTS; i++) { + // if (!light[i]) continue; + for (int i = 0; i < lightCount; i++) { + + float denom = lightFalloffConstant[i]; + float spotTerm = 1; + + if (lightType[i] == AMBIENT) { + if (lightFalloffQuadratic[i] != 0 || lightFalloffLinear[i] != 0) { + // Falloff depends on distance + float distSq = mag(lightPosition[i][0] - wx, + lightPosition[i][1] - wy, + lightPosition[i][2] - wz); + denom += + lightFalloffQuadratic[i] * distSq + + lightFalloffLinear[i] * (float) sqrt(distSq); + } + if (denom == 0) denom = 1; + + contribution[LIGHT_AMBIENT_R] += lightDiffuse[i][0] / denom; + contribution[LIGHT_AMBIENT_G] += lightDiffuse[i][1] / denom; + contribution[LIGHT_AMBIENT_B] += lightDiffuse[i][2] / denom; + + } else { + // If not ambient, we must deal with direction + + // li is the vector from the vertex to the light + float lix, liy, liz; + float lightDir_dot_li = 0; + float n_dot_li = 0; + + if (lightType[i] == DIRECTIONAL) { + lix = -lightNormal[i][0]; + liy = -lightNormal[i][1]; + liz = -lightNormal[i][2]; + denom = 1; + n_dot_li = (nx * lix + ny * liy + nz * liz); + // If light is lighting the face away from the camera, ditch + if (n_dot_li <= 0) { + continue; + } + } else { // Point or spot light (must deal also with light location) + lix = lightPosition[i][0] - wx; + liy = lightPosition[i][1] - wy; + liz = lightPosition[i][2] - wz; + // normalize + float distSq = mag(lix, liy, liz); + if (distSq != 0) { + lix /= distSq; + liy /= distSq; + liz /= distSq; + } + n_dot_li = (nx * lix + ny * liy + nz * liz); + // If light is lighting the face away from the camera, ditch + if (n_dot_li <= 0) { + continue; + } + + if (lightType[i] == SPOT) { // Must deal with spot cone + lightDir_dot_li = + -(lightNormal[i][0] * lix + + lightNormal[i][1] * liy + + lightNormal[i][2] * liz); + // Outside of spot cone + if (lightDir_dot_li <= lightSpotAngleCos[i]) { + continue; + } + spotTerm = pow(lightDir_dot_li, lightSpotConcentration[i]); + } + + if (lightFalloffQuadratic[i] != 0 || lightFalloffLinear[i] != 0) { + // Falloff depends on distance + denom += + lightFalloffQuadratic[i] * distSq + + lightFalloffLinear[i] * (float) sqrt(distSq); + } + } + // Directional, point, or spot light: + + // We know n_dot_li > 0 from above "continues" + + if (denom == 0) + denom = 1; + float mul = n_dot_li * spotTerm / denom; + contribution[LIGHT_DIFFUSE_R] += lightDiffuse[i][0] * mul; + contribution[LIGHT_DIFFUSE_G] += lightDiffuse[i][1] * mul; + contribution[LIGHT_DIFFUSE_B] += lightDiffuse[i][2] * mul; + + // SPECULAR + + // If the material and light have a specular component. + if ((sr > 0 || sg > 0 || sb > 0) && + (lightSpecular[i][0] > 0 || + lightSpecular[i][1] > 0 || + lightSpecular[i][2] > 0)) { + + float vmag = mag(wx, wy, wz); + if (vmag != 0) { + wx /= vmag; + wy /= vmag; + wz /= vmag; + } + float sx = lix - wx; + float sy = liy - wy; + float sz = liz - wz; + vmag = mag(sx, sy, sz); + if (vmag != 0) { + sx /= vmag; + sy /= vmag; + sz /= vmag; + } + float s_dot_n = (sx * nx + sy * ny + sz * nz); + + if (s_dot_n > 0) { + s_dot_n = pow(s_dot_n, shine); + mul = s_dot_n * spotTerm / denom; + contribution[LIGHT_SPECULAR_R] += lightSpecular[i][0] * mul; + contribution[LIGHT_SPECULAR_G] += lightSpecular[i][1] * mul; + contribution[LIGHT_SPECULAR_B] += lightSpecular[i][2] * mul; + } + + } + } + } + /*target[toffset + 0] = min(1, er + dr * diffuse_r); + target[toffset + 1] = min(1, eg + dg * diffuse_g); + target[toffset + 2] = min(1, eb + db * diffuse_b); + + target[SPR] = min(1, sr * specular_r); + target[SPG] = min(1, sg * specular_g); + target[SPB] = min(1, sb * specular_b);*/ + return; + } + + + // Multiply the lighting contribution into the vertex's colors. + // Only do this when there is ONE lighting per vertex + // (MANUAL_VERTEX_NORMAL or SHAPE_NORMAL mode). + private void apply_lighting_contribution(int vIndex, float[] contribution) { + float[] v = vertices[vIndex]; + + v[R] = min(1, v[ER] + v[AR] * contribution[LIGHT_AMBIENT_R] + + v[DR] * contribution[LIGHT_DIFFUSE_R]); + v[G] = min(1, v[EG] + v[AG] * contribution[LIGHT_AMBIENT_G] + + v[DG] * contribution[LIGHT_DIFFUSE_G]); + v[B] = min(1, v[EB] + v[AB] * contribution[LIGHT_AMBIENT_B] + + v[DB] * contribution[LIGHT_DIFFUSE_B]); + v[A] = min(1, v[DA]); + + v[SPR] = min(1, v[SPR] * contribution[LIGHT_SPECULAR_R]); + v[SPG] = min(1, v[SPG] * contribution[LIGHT_SPECULAR_G]); + v[SPB] = min(1, v[SPB] * contribution[LIGHT_SPECULAR_B]); + v[SPA] = min(1, v[SPA]); + + v[BEEN_LIT] = 1; + } + + + private void light_vertex_always(int vIndex, float[] contribution) { + calc_lighting_contribution(vIndex, contribution); + apply_lighting_contribution(vIndex, contribution); + } + + + private void light_vertex_if_not_already_lit(int vIndex, + float[] contribution) { + if (vertices[vIndex][BEEN_LIT] == 0) { + light_vertex_always(vIndex, contribution); + } + } + + + private void copy_prelit_vertex_color_to_triangle(int triIndex, int vIndex, + int colorIndex) { + float[] triColor = triangleColors[triIndex][colorIndex]; + float[] v = vertices[vIndex]; + + triColor[TRI_DIFFUSE_R] = v[R]; + triColor[TRI_DIFFUSE_G] = v[G]; + triColor[TRI_DIFFUSE_B] = v[B]; + triColor[TRI_DIFFUSE_A] = v[A]; + triColor[TRI_SPECULAR_R] = v[SPR]; + triColor[TRI_SPECULAR_G] = v[SPG]; + triColor[TRI_SPECULAR_B] = v[SPB]; + triColor[TRI_SPECULAR_A] = v[SPA]; + } + + + private void copy_vertex_color_to_triangle(int triIndex, + int vIndex, int colorIndex, + float[] lightContribution) { + float[] triColor = triangleColors[triIndex][colorIndex]; + float[] v = vertices[vIndex]; + + triColor[TRI_DIFFUSE_R] = + min(1, v[ER] + v[AR] * lightContribution[LIGHT_AMBIENT_R] + + v[DR] * lightContribution[LIGHT_DIFFUSE_R]); + triColor[TRI_DIFFUSE_G] = + min(1, v[EG] + v[AG] * lightContribution[LIGHT_AMBIENT_G] + + v[DG] * lightContribution[LIGHT_DIFFUSE_G]); + triColor[TRI_DIFFUSE_B] = + min(1, v[EB] + v[AB] * lightContribution[LIGHT_AMBIENT_B] + + v[DB] * lightContribution[LIGHT_DIFFUSE_B]); + triColor[TRI_DIFFUSE_A] = min(1, v[DA]); + + triColor[TRI_SPECULAR_R] = + min(1, v[SPR] * lightContribution[LIGHT_SPECULAR_R]); + triColor[TRI_SPECULAR_G] = + min(1, v[SPG] * lightContribution[LIGHT_SPECULAR_G]); + triColor[TRI_SPECULAR_B] = + min(1, v[SPB] * lightContribution[LIGHT_SPECULAR_B]); + triColor[TRI_SPECULAR_A] = min(1, v[SPA]); + } + + + private void light_triangle(int triIndex, float[] lightContribution) { + int vIndex = triangles[triIndex][VERTEX1]; + copy_vertex_color_to_triangle(triIndex, vIndex, 0, lightContribution); + vIndex = triangles[triIndex][VERTEX2]; + copy_vertex_color_to_triangle(triIndex, vIndex, 1, lightContribution); + vIndex = triangles[triIndex][VERTEX3]; + copy_vertex_color_to_triangle(triIndex, vIndex, 2, lightContribution); + } + + + private void crossProduct(float[] u, float[] v, float[] out) { + out[0] = u[1]*v[2] - u[2]*v[1]; + out[1] = u[2]*v[0] - u[0]*v[2]; + out[2] = u[0]*v[1] - u[1]*v[0]; + } + + + private void light_triangle(int triIndex) { + int vIndex; + + // Handle lighting on, but no lights (in this case, just use emissive) + // This wont be used currently because lightCount == 0 is don't use + // lighting at all... So. OK. If that ever changes, use the below: + /* + if (lightCount == 0) { + vIndex = triangles[triIndex][VERTEX1]; + copy_emissive_vertex_color_to_triangle(triIndex, vIndex, 0); + vIndex = triangles[triIndex][VERTEX2]; + copy_emissive_vertex_color_to_triangle(triIndex, vIndex, 1); + vIndex = triangles[triIndex][VERTEX3]; + copy_emissive_vertex_color_to_triangle(triIndex, vIndex, 2); + return; + } + */ + + // In MANUAL_VERTEX_NORMAL mode, we have a specific normal + // for each vertex. In that case, we light any verts that + // haven't already been lit and copy their colors straight + // into the triangle. + if (normalMode == MANUAL_VERTEX_NORMAL) { + vIndex = triangles[triIndex][VERTEX1]; + light_vertex_if_not_already_lit(vIndex, tempLightingContribution); + copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 0); + + vIndex = triangles[triIndex][VERTEX2]; + light_vertex_if_not_already_lit(vIndex, tempLightingContribution); + copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 1); + + vIndex = triangles[triIndex][VERTEX3]; + light_vertex_if_not_already_lit(vIndex, tempLightingContribution); + copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 2); + + } + + // If the lighting doesn't depend on the vertex position, do the + // following: We've already dealt with MANUAL_SHAPE_NORMAL mode before + // we got into this function, so here we only have to deal with + // AUTO_NORMAL mode. So we calculate the normal for this triangle, + // and use that for the lighting. + else if (!lightingDependsOnVertexPosition) { + vIndex = triangles[triIndex][VERTEX1]; + int vIndex2 = triangles[triIndex][VERTEX2]; + int vIndex3 = triangles[triIndex][VERTEX3]; + + /* + float[] dv1 = new float[] {vertices[vIndex2][VX] - vertices[vIndex][VX], + vertices[vIndex2][VY] - vertices[vIndex][VY], + vertices[vIndex2][VZ] - vertices[vIndex][VZ]}; + float[] dv2 = new float[] {vertices[vIndex3][VX] - vertices[vIndex][VX], + vertices[vIndex3][VY] - vertices[vIndex][VY], + vertices[vIndex3][VZ] - vertices[vIndex][VZ]}; + */ + dv1[0] = vertices[vIndex2][VX] - vertices[vIndex][VX]; + dv1[1] = vertices[vIndex2][VY] - vertices[vIndex][VY]; + dv1[2] = vertices[vIndex2][VZ] - vertices[vIndex][VZ]; + + dv2[0] = vertices[vIndex3][VX] - vertices[vIndex][VX]; + dv2[1] = vertices[vIndex3][VY] - vertices[vIndex][VY]; + dv2[2] = vertices[vIndex3][VZ] - vertices[vIndex][VZ]; + + //float[] norm = new float[3]; + crossProduct(dv1, dv2, norm); + float nMag = mag(norm[X], norm[Y], norm[Z]); + if (nMag != 0 && nMag != 1) { + norm[X] /= nMag; norm[Y] /= nMag; norm[Z] /= nMag; + } + vertices[vIndex][NX] = norm[X]; + vertices[vIndex][NY] = norm[Y]; + vertices[vIndex][NZ] = norm[Z]; + + // The true at the end says the normal is already in world coordinates + calc_lighting_contribution(vIndex, tempLightingContribution, true); + copy_vertex_color_to_triangle(triIndex, vIndex, 0, + tempLightingContribution); + copy_vertex_color_to_triangle(triIndex, vIndex2, 1, + tempLightingContribution); + copy_vertex_color_to_triangle(triIndex, vIndex3, 2, + tempLightingContribution); + } + + // If lighting is position-dependent + else { + if (normalMode == MANUAL_SHAPE_NORMAL) { + vIndex = triangles[triIndex][VERTEX1]; + vertices[vIndex][NX] = vertices[vertex_start][NX]; + vertices[vIndex][NY] = vertices[vertex_start][NY]; + vertices[vIndex][NZ] = vertices[vertex_start][NZ]; + calc_lighting_contribution(vIndex, tempLightingContribution); + copy_vertex_color_to_triangle(triIndex, vIndex, 0, + tempLightingContribution); + + vIndex = triangles[triIndex][VERTEX2]; + vertices[vIndex][NX] = vertices[vertex_start][NX]; + vertices[vIndex][NY] = vertices[vertex_start][NY]; + vertices[vIndex][NZ] = vertices[vertex_start][NZ]; + calc_lighting_contribution(vIndex, tempLightingContribution); + copy_vertex_color_to_triangle(triIndex, vIndex, 1, + tempLightingContribution); + + vIndex = triangles[triIndex][VERTEX3]; + vertices[vIndex][NX] = vertices[vertex_start][NX]; + vertices[vIndex][NY] = vertices[vertex_start][NY]; + vertices[vIndex][NZ] = vertices[vertex_start][NZ]; + calc_lighting_contribution(vIndex, tempLightingContribution); + copy_vertex_color_to_triangle(triIndex, vIndex, 2, + tempLightingContribution); + } + + // lighting mode is AUTO_NORMAL + else { + vIndex = triangles[triIndex][VERTEX1]; + int vIndex2 = triangles[triIndex][VERTEX2]; + int vIndex3 = triangles[triIndex][VERTEX3]; + /* + float[] dv1 = new float[] {vertices[vIndex2][VX] - vertices[vIndex][VX], + vertices[vIndex2][VY] - vertices[vIndex][VY], + vertices[vIndex2][VZ] - vertices[vIndex][VZ]}; + float[] dv2 = new float[] {vertices[vIndex3][VX] - vertices[vIndex][VX], + vertices[vIndex3][VY] - vertices[vIndex][VY], + vertices[vIndex3][VZ] - vertices[vIndex][VZ]}; + */ + dv1[0] = vertices[vIndex2][VX] - vertices[vIndex][VX]; + dv1[1] = vertices[vIndex2][VY] - vertices[vIndex][VY]; + dv1[2] = vertices[vIndex2][VZ] - vertices[vIndex][VZ]; + dv2[0] = vertices[vIndex3][VX] - vertices[vIndex][VX]; + dv2[1] = vertices[vIndex3][VY] - vertices[vIndex][VY]; + dv2[2] = vertices[vIndex3][VZ] - vertices[vIndex][VZ]; + + //float[] norm = new float[3]; + crossProduct(dv1, dv2, norm); + float nMag = mag(norm[X], norm[Y], norm[Z]); + if (nMag != 0 && nMag != 1) { + norm[X] /= nMag; norm[Y] /= nMag; norm[Z] /= nMag; + } + vertices[vIndex][NX] = norm[X]; + vertices[vIndex][NY] = norm[Y]; + vertices[vIndex][NZ] = norm[Z]; + // The true at the end says the normal is already in world coordinates + calc_lighting_contribution(vIndex, tempLightingContribution, true); + copy_vertex_color_to_triangle(triIndex, vIndex, 0, + tempLightingContribution); + + vertices[vIndex2][NX] = norm[X]; + vertices[vIndex2][NY] = norm[Y]; + vertices[vIndex2][NZ] = norm[Z]; + // The true at the end says the normal is already in world coordinates + calc_lighting_contribution(vIndex2, tempLightingContribution, true); + copy_vertex_color_to_triangle(triIndex, vIndex2, 1, + tempLightingContribution); + + vertices[vIndex3][NX] = norm[X]; + vertices[vIndex3][NY] = norm[Y]; + vertices[vIndex3][NZ] = norm[Z]; + // The true at the end says the normal is already in world coordinates + calc_lighting_contribution(vIndex3, tempLightingContribution, true); + copy_vertex_color_to_triangle(triIndex, vIndex3, 2, + tempLightingContribution); + } + } + } + + + protected void handle_lighting() { + + // If the lighting does not depend on vertex position and there is a single + // normal specified for this shape, go ahead and apply the same lighting + // contribution to every vertex in this shape (one lighting calc!) + if (!lightingDependsOnVertexPosition && normalMode == MANUAL_SHAPE_NORMAL) { + calc_lighting_contribution(vertex_start, tempLightingContribution); + for (int tri = 0; tri < triangleCount; tri++) { + light_triangle(tri, tempLightingContribution); + } + } + // Otherwise light each triangle individually... + else { + for (int tri = 0; tri < triangleCount; tri++) { + light_triangle(tri); + } + } + } + + + protected void handle_no_lighting() { + int vIndex; + for (int tri = 0; tri < triangleCount; tri++) { + vIndex = triangles[tri][VERTEX1]; + copy_prelit_vertex_color_to_triangle(tri, vIndex, 0); + vIndex = triangles[tri][VERTEX2]; + copy_prelit_vertex_color_to_triangle(tri, vIndex, 1); + vIndex = triangles[tri][VERTEX3]; + copy_prelit_vertex_color_to_triangle(tri, vIndex, 2); + } + } + + + + ////////////////////////////////////////////////////////////// + + // BASIC SHAPES + + + public void point(float x, float y) { + point(x, y, 0); + } + + + public void point(float x, float y, float z) { + /* + beginShape(POINTS); + vertex(x, y, z); + endShape(); + */ + + // hacked workaround for carlos line bug + beginShape(LINES); + vertex(x, y, z); + vertex(x + EPSILON, y + EPSILON, z); + endShape(); + } + + /* + private void point3(float x, float y, float z, int color) { + // need to get scaled version of the stroke + float x1 = screenX(x - 0.5f, y - 0.5f, z); + float y1 = screenY(x - 0.5f, y - 0.5f, z); + float x2 = screenX(x + 0.5f, y + 0.5f, z); + float y2 = screenY(x + 0.5f, y + 0.5f, z); + + float weight = (abs(x2 - x1) + abs(y2 - y1)) / 2f; + if (weight < 1.5f) { + int xx = (int) ((x1 + x2) / 2f); + int yy = (int) ((y1 + y2) / 2f); + //point0(xx, yy, z, color); + zbuffer[yy*width + xx] = screenZ(x, y, z); + //stencil? + + } else { + // actually has some weight, need to draw shapes instead + // these will be + } + } + */ + + + /** + * Compared to the implementation in PGraphics, this adds normal(). + */ + public void triangle(float x1, float y1, float x2, float y2, + float x3, float y3) { + beginShape(TRIANGLES); + normal(0, 0, 1); + vertex(x1, y1); + vertex(x2, y2); + vertex(x3, y3); + endShape(); + } + + + /** + * Compared to the implementation in PGraphics, this adds normal(). + */ + public void quad(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + beginShape(QUADS); + normal(0, 0, 1); + vertex(x1, y1); + vertex(x2, y2); + vertex(x3, y3); + vertex(x4, y4); + endShape(); + } + + + + ////////////////////////////////////////////////////////////// + + // BOX + + + public void box(float size) { + box(size, size, size); + } + + + // OPT this isn't the least bit efficient + // because it redraws lines along the vertices + // ugly ugly ugly! + public void box(float w, float h, float d) { + float x1 = -w/2f; float x2 = w/2f; + float y1 = -h/2f; float y2 = h/2f; + float z1 = -d/2f; float z2 = d/2f; + + if (triangle != null) { // triangle is null in gl + triangle.setCulling(true); + } + + beginShape(QUADS); + + // front + normal(0, 0, 1); + vertex(x1, y1, z1); + vertex(x2, y1, z1); + vertex(x2, y2, z1); + vertex(x1, y2, z1); + + // right + normal(1, 0, 0); + vertex(x2, y1, z1); + vertex(x2, y1, z2); + vertex(x2, y2, z2); + vertex(x2, y2, z1); + + // back + normal(0, 0, -1); + vertex(x2, y1, z2); + vertex(x1, y1, z2); + vertex(x1, y2, z2); + vertex(x2, y2, z2); + + // left + normal(-1, 0, 0); + vertex(x1, y1, z2); + vertex(x1, y1, z1); + vertex(x1, y2, z1); + vertex(x1, y2, z2); + + // top + normal(0, 1, 0); + vertex(x1, y1, z2); + vertex(x2, y1, z2); + vertex(x2, y1, z1); + vertex(x1, y1, z1); + + // bottom + normal(0, -1, 0); + vertex(x1, y2, z1); + vertex(x2, y2, z1); + vertex(x2, y2, z2); + vertex(x1, y2, z2); + + endShape(); + + if (triangle != null) { // triangle is null in gl + triangle.setCulling(false); + } + } + + + + ////////////////////////////////////////////////////////////// + + // SPHERE + + + // [toxi031031] used by the new sphere code below + // precompute vertices along unit sphere with new detail setting + + public void sphereDetail(int res) { + if (res < 3) res = 3; // force a minimum res + if (res == sphereDetail) return; + + float delta = (float)SINCOS_LENGTH/res; + float[] cx = new float[res]; + float[] cz = new float[res]; + // calc unit circle in XZ plane + for (int i = 0; i < res; i++) { + cx[i] = cosLUT[(int) (i*delta) % SINCOS_LENGTH]; + cz[i] = sinLUT[(int) (i*delta) % SINCOS_LENGTH]; + } + // computing vertexlist + // vertexlist starts at south pole + int vertCount = res * (res-1) + 2; + int currVert = 0; + + // re-init arrays to store vertices + sphereX = new float[vertCount]; + sphereY = new float[vertCount]; + sphereZ = new float[vertCount]; + + float angle_step = (SINCOS_LENGTH*0.5f)/res; + float angle = angle_step; + + // step along Y axis + for (int i = 1; i < res; i++) { + float curradius = sinLUT[(int) angle % SINCOS_LENGTH]; + float currY = -cosLUT[(int) angle % SINCOS_LENGTH]; + for (int j = 0; j < res; j++) { + sphereX[currVert] = cx[j] * curradius; + sphereY[currVert] = currY; + sphereZ[currVert++] = cz[j] * curradius; + } + angle += angle_step; + } + sphereDetail = res; + } + + + /** + * Draw a sphere with radius r centered at coordinate 0, 0, 0. + *

+ * Implementation notes: + *

+ * cache all the points of the sphere in a static array + * top and bottom are just a bunch of triangles that land + * in the center point + *

+ * sphere is a series of concentric circles who radii vary + * along the shape, based on, er.. cos or something + *

+   * [toxi031031] new sphere code. removed all multiplies with
+   * radius, as scale() will take care of that anyway
+   *
+   * [toxi031223] updated sphere code (removed modulos)
+   * and introduced sphereAt(x,y,z,r)
+   * to avoid additional translate()'s on the user/sketch side
+   * 
+ */ + public void sphere(float r) { + float x = 0; // TODO clean this back up again + float y = 0; + float z = 0; + + if (sphereDetail == 0) { + sphereDetail(30); + } + + int v1,v11,v2; + pushMatrix(); + if (x!=0f && y!=0f && z!=0f) translate(x,y,z); + scale(r); + + if (triangle != null) { // triangle is null in gl + triangle.setCulling(true); + } + + // 1st ring from south pole + beginShape(TRIANGLE_STRIP); + for (int i = 0; i < sphereDetail; i++) { + normal(0, -1, 0); + vertex(0, -1, 0); + normal(sphereX[i], sphereY[i], sphereZ[i]); + vertex(sphereX[i], sphereY[i], sphereZ[i]); + } + //normal(0, -1, 0); + vertex(0, -1, 0); + normal(sphereX[0], sphereY[0], sphereZ[0]); + vertex(sphereX[0], sphereY[0], sphereZ[0]); + endShape(); + + // middle rings + int voff = 0; + for(int i = 2; i < sphereDetail; i++) { + v1=v11=voff; + voff += sphereDetail; + v2=voff; + beginShape(TRIANGLE_STRIP); + for (int j = 0; j < sphereDetail; j++) { + normal(sphereX[v1], sphereY[v1], sphereZ[v1]); + vertex(sphereX[v1], sphereY[v1], sphereZ[v1++]); + normal(sphereX[v2], sphereY[v2], sphereZ[v2]); + vertex(sphereX[v2], sphereY[v2], sphereZ[v2++]); + } + // close each ring + v1=v11; + v2=voff; + normal(sphereX[v1], sphereY[v1], sphereZ[v1]); + vertex(sphereX[v1], sphereY[v1], sphereZ[v1]); + normal(sphereX[v2], sphereY[v2], sphereZ[v2]); + vertex(sphereX[v2], sphereY[v2], sphereZ[v2]); + endShape(); + } + + // add the northern cap + beginShape(TRIANGLE_STRIP); + for (int i = 0; i < sphereDetail; i++) { + v2 = voff + i; + normal(sphereX[v2], sphereY[v2], sphereZ[v2]); + vertex(sphereX[v2], sphereY[v2], sphereZ[v2]); + normal(0, 1, 0); + vertex(0, 1, 0); + } + normal(sphereX[voff], sphereY[voff], sphereZ[voff]); + vertex(sphereX[voff], sphereY[voff], sphereZ[voff]); + normal(0, 1, 0); + vertex(0, 1, 0); + endShape(); + popMatrix(); + + if (triangle != null) { // triangle is null in gl + triangle.setCulling(false); + } + } + + + + ////////////////////////////////////////////////////////////// + + // CURVES + + /* + + public void bezier(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + bezier(x1, y1, 0, + x2, y2, 0, + x3, y3, 0, + x4, y4, 0); + } + + + public void bezier(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + beginShape(LINE_STRIP); + vertex(x1, y1, z1); + bezierVertex(x2, y2, z2, + x3, y3, z3, + x4, y4, z4); + endShape(); + } + + + public void curve(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + curve(x1, y1, 0, + x2, y2, 0, + x3, y3, 0, + x4, y4, 0); + } + + + public void curve(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + beginShape(LINE_STRIP); + curveVertex(x1, y1, z1); + curveVertex(x2, y2, z2); + curveVertex(x3, y3, z3); + curveVertex(x4, y4, z4); + endShape(); + } + + */ + + + + ////////////////////////////////////////////////////////////// + + // MATRIX TRANSFORMATIONS + + + public void translate(float tx, float ty) { + translate(tx, ty, 0); + } + + + public void translate(float tx, float ty, float tz) { + forwardTransform.translate(tx, ty, tz); + reverseTransform.invTranslate(tx, ty, tz); + } + + + /** + * Two dimensional rotation. Same as rotateZ (this is identical + * to a 3D rotation along the z-axis) but included for clarity -- + * it'd be weird for people drawing 2D graphics to be using rotateZ. + * And they might kick our a-- for the confusion. + */ + public void rotate(float angle) { + rotateZ(angle); + } + + + // OPT could save several multiplies for the 0s and 1s by just + // putting the multMatrix code here and removing uneccessary terms + + public void rotateX(float angle) { + forwardTransform.rotateX(angle); + reverseTransform.invRotateX(angle); + } + + + public void rotateY(float angle) { + forwardTransform.rotateY(angle); + reverseTransform.invRotateY(angle); + } + + + /** + * Rotate in the XY plane by an angle. + * + * Note that this doesn't internally set the number of + * dimensions to three, since rotateZ() is the same as a + * 2D rotate in the XY plane. + */ + public void rotateZ(float angle) { + forwardTransform.rotateZ(angle); + reverseTransform.invRotateZ(angle); + } + + + /** + * Rotate around an arbitrary vector, similar to glRotate(), + * except that it takes radians (instead of degrees). + */ + public void rotate(float angle, float v0, float v1, float v2) { + forwardTransform.rotate(angle, v0, v1, v2); + reverseTransform.invRotate(angle, v0, v1, v2); + } + + + /** + * Same as scale(s, s, s); + */ + public void scale(float s) { + scale(s, s, s); + } + + + /** + * Not recommended for use in 3D, because the z-dimension is just + * scaled by 1, since there's no way to know what else to scale it by. + * Equivalent to scale(sx, sy, 1); + */ + public void scale(float sx, float sy) { + scale(sx, sy, 1); + } + + + /** + * Scale in three dimensions. + */ + public void scale(float x, float y, float z) { + forwardTransform.scale(x, y, z); + reverseTransform.invScale(x, y, z); + } + + + + ////////////////////////////////////////////////////////////// + + // TRANSFORMATION MATRIX + + + public void pushMatrix() { + if (!modelview.push()) { + throw new RuntimeException("Too many calls to pushMatrix()"); + } + // Do this to the inverse regardless of the lights + // to keep stack pointers in sync + modelviewInv.push(); + } + + + public void popMatrix() { + if (!modelview.pop()) { + throw new RuntimeException("Too many calls to popMatrix() " + + "(and not enough to pushMatrix)"); + } + // Do this to the inverse regardless of the lights + // to keep stack pointers in sync + modelviewInv.pop(); + } + + + /** + * Load identity as the transform/model matrix. + * Same as glLoadIdentity(). + */ + public void resetMatrix() { + forwardTransform.reset(); + reverseTransform.reset(); + } + + + public void applyMatrix(float n00, float n01, float n02, + float n10, float n11, float n12) { + throw new RuntimeException("Use applyMatrix() with a 4x4 matrix " + + "when using OPENGL or P3D"); + } + + /** + * Apply a 4x4 transformation matrix. Same as glMultMatrix(). + * This call will be slow because it will try to calculate the + * inverse of the transform. So avoid it whenever possible. + */ + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + + forwardTransform.apply(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + + reverseTransform.invApply(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + } + + + /** + * Load the modelview into m00, m01, et al so that it can be used. + */ + public void loadMatrix() { + m00 = modelview.m00; + m01 = modelview.m01; + m02 = modelview.m02; + m03 = modelview.m03; + + m10 = modelview.m10; + m11 = modelview.m11; + m12 = modelview.m12; + m13 = modelview.m13; + + m20 = modelview.m20; + m21 = modelview.m21; + m22 = modelview.m22; + m23 = modelview.m23; + + m30 = modelview.m30; + m31 = modelview.m31; + m32 = modelview.m32; + m33 = modelview.m33; + } + + + /** + * Print the current model (or "transformation") matrix. + */ + public void printMatrix() { + modelview.print(); + } + + + + ////////////////////////////////////////////////////////////// + + // CAMERA and PERSPECTIVE + + + /** + * Set matrix mode to the camera matrix (instead of the current + * transformation matrix). This means applyMatrix, resetMatrix, etc. + * will affect the camera. + *

+ * Note that the camera matrix is *not* the perspective matrix, + * it is in front of the modelview matrix (hence the name "model" + * and "view" for that matrix). + *

+ * beginCamera() specifies that all coordinate transforms until endCamera() + * should be pre-applied in inverse to the camera transform matrix. + * Note that this is only challenging when a user specifies an arbitrary + * matrix with applyMatrix(). Then that matrix will need to be inverted, + * which may not be possible. But take heart, if a user is applying a + * non-invertible matrix to the camera transform, then he is clearly + * up to no good, and we can wash our hands of those bad intentions. + *

+ * begin/endCamera clauses do not automatically reset the camera transform + * matrix. That's because we set up a nice default camera transform int + * setup(), and we expect it to hold through draw(). So we don't reset + * the camera transform matrix at the top of draw(). That means that an + * innocuous-looking clause like + *

+   * beginCamera();
+   * translate(0, 0, 10);
+   * endCamera();
+   * 
+ * at the top of draw(), will result in a runaway camera that shoots + * infinitely out of the screen over time. In order to prevent this, + * it is necessary to call some function that does a hard reset of the + * camera transform matrix inside of begin/endCamera. Two options are + *
+   * camera(); // sets up the nice default camera transform
+   * resetMatrix(); // sets up the identity camera transform
+   * 
+ * So to rotate a camera a constant amount, you might try + *
+   * beginCamera();
+   * camera();
+   * rotateY(PI/8);
+   * endCamera();
+   * 
+ */ + public void beginCamera() { + if (manipulatingCamera) { + throw new RuntimeException("beginCamera() cannot be called again " + + "before endCamera()"); + } else { + manipulatingCamera = true; + forwardTransform = cameraInv; + reverseTransform = camera; + } + } + + + /** + * Record the current settings into the camera matrix, and set + * the matrix mode back to the current transformation matrix. + *

+ * Note that this will destroy any settings to scale(), translate(), + * or whatever, because the final camera matrix will be copied + * (not multiplied) into the modelview. + */ + public void endCamera() { + if (!manipulatingCamera) { + throw new RuntimeException("Cannot call endCamera() " + + "without first calling beginCamera()"); + } + // reset the modelview to use this new camera matrix + modelview.set(camera); + modelviewInv.set(cameraInv); + + // set matrix mode back to modelview + forwardTransform = modelview; + reverseTransform = modelviewInv; + + // all done + manipulatingCamera = false; + } + + + /** + * Set camera to the default settings. + *

+ * Processing camera behavior: + *

+ * Camera behavior can be split into two separate components, camera + * transformation, and projection. The transformation corresponds to the + * physical location, orientation, and scale of the camera. In a physical + * camera metaphor, this is what can manipulated by handling the camera + * body (with the exception of scale, which doesn't really have a physcial + * analog). The projection corresponds to what can be changed by + * manipulating the lens. + *

+ * We maintain separate matrices to represent the camera transform and + * projection. An important distinction between the two is that the camera + * transform should be invertible, where the projection matrix should not, + * since it serves to map three dimensions to two. It is possible to bake + * the two matrices into a single one just by multiplying them together, + * but it isn't a good idea, since lighting, z-ordering, and z-buffering + * all demand a true camera z coordinate after modelview and camera + * transforms have been applied but before projection. If the camera + * transform and projection are combined there is no way to recover a + * good camera-space z-coordinate from a model coordinate. + *

+ * Fortunately, there are no functions that manipulate both camera + * transformation and projection. + *

+ * camera() sets the camera position, orientation, and center of the scene. + * It replaces the camera transform with a new one. This is different from + * gluLookAt(), but I think the only reason that GLU's lookat doesn't fully + * replace the camera matrix with the new one, but instead multiplies it, + * is that GL doesn't enforce the separation of camera transform and + * projection, so it wouldn't be safe (you'd probably stomp your projection). + *

+ * The transformation functions are the same ones used to manipulate the + * modelview matrix (scale, translate, rotate, etc.). But they are bracketed + * with beginCamera(), endCamera() to indicate that they should apply + * (in inverse), to the camera transformation matrix. + *

+ * This differs considerably from camera transformation in OpenGL. + * OpenGL only lets you say, apply everything from here out to the + * projection or modelview matrix. This makes it very hard to treat camera + * manipulation as if it were a physical camera. Imagine that you want to + * move your camera 100 units forward. In OpenGL, you need to apply the + * inverse of that transformation or else you'll move your scene 100 units + * forward--whether or not you've specified modelview or projection matrix. + * Remember they're just multiplied by model coods one after another. + * So in order to treat a camera like a physical camera, it is necessary + * to pre-apply inverse transforms to a matrix that will be applied to model + * coordinates. OpenGL provides nothing of this sort, but Processing does! + * This is the camera transform matrix. + */ + public void camera() { + camera(cameraX, cameraY, cameraZ, + cameraX, cameraY, 0, + 0, 1, 0); + } + + + /** + * More flexible method for dealing with camera(). + *

+ * The actual call is like gluLookat. Here's the real skinny on + * what does what: + *

+   * camera(); or
+   * camera(ex, ey, ez, cx, cy, cz, ux, uy, uz);
+   * 
+ * do not need to be called from with beginCamera();/endCamera(); + * That's because they always apply to the camera transformation, + * and they always totally replace it. That means that any coordinate + * transforms done before camera(); in draw() will be wiped out. + * It also means that camera() always operates in untransformed world + * coordinates. Therefore it is always redundant to call resetMatrix(); + * before camera(); This isn't technically true of gluLookat, but it's + * pretty much how it's used. + *

+ * Now, beginCamera(); and endCamera(); are useful if you want to move + * the camera around using transforms like translate(), etc. They will + * wipe out any coordinate system transforms that occur before them in + * draw(), but they will not automatically wipe out the camera transform. + * This means that they should be at the top of draw(). It also means + * that the following: + *

+   * beginCamera();
+   * rotateY(PI/8);
+   * endCamera();
+   * 
+ * will result in a camera that spins without stopping. If you want to + * just rotate a small constant amount, try this: + *
+   * beginCamera();
+   * camera(); // sets up the default view
+   * rotateY(PI/8);
+   * endCamera();
+   * 
+ * That will rotate a little off of the default view. Note that this + * is entirely equivalent to + *
+   * camera(); // sets up the default view
+   * beginCamera();
+   * rotateY(PI/8);
+   * endCamera();
+   * 
+ * because camera() doesn't care whether or not it's inside a + * begin/end clause. Basically it's safe to use camera() or + * camera(ex, ey, ez, cx, cy, cz, ux, uy, uz) as naked calls because + * they do all the matrix resetting automatically. + */ + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + float z0 = eyeX - centerX; + float z1 = eyeY - centerY; + float z2 = eyeZ - centerZ; + float mag = sqrt(z0*z0 + z1*z1 + z2*z2); + + if (mag != 0) { + z0 /= mag; + z1 /= mag; + z2 /= mag; + } + + float y0 = upX; + float y1 = upY; + float y2 = upZ; + + float x0 = y1*z2 - y2*z1; + float x1 = -y0*z2 + y2*z0; + float x2 = y0*z1 - y1*z0; + + y0 = z1*x2 - z2*x1; + y1 = -z0*x2 + z2*x0; + y2 = z0*x1 - z1*x0; + + mag = sqrt(x0*x0 + x1*x1 + x2*x2); + if (mag != 0) { + x0 /= mag; + x1 /= mag; + x2 /= mag; + } + + mag = sqrt(y0*y0 + y1*y1 + y2*y2); + if (mag != 0) { + y0 /= mag; + y1 /= mag; + y2 /= mag; + } + + // just does an apply to the main matrix, + // since that'll be copied out on endCamera + camera.set(x0, x1, x2, 0, + y0, y1, y2, 0, + z0, z1, z2, 0, + 0, 0, 0, 1); + camera.translate(-eyeX, -eyeY, -eyeZ); + + cameraInv.reset(); + cameraInv.invApply(x0, x1, x2, 0, + y0, y1, y2, 0, + z0, z1, z2, 0, + 0, 0, 0, 1); + cameraInv.invTranslate(-eyeX, -eyeY, -eyeZ); + + modelview.set(camera); + modelviewInv.set(cameraInv); + } + + + /** + * Print the current camera matrix. + */ + public void printCamera() { + camera.print(); + } + + + /** + * Calls ortho() with the proper parameters for Processing's + * standard orthographic projection. + */ + public void ortho() { + ortho(0, width, 0, height, -10, 10); + } + + + /** + * Similar to gluOrtho(), but wipes out the current projection matrix. + *

+ * Implementation partially based on Mesa's matrix.c. + */ + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + float x = 2.0f / (right - left); + float y = 2.0f / (top - bottom); + float z = -2.0f / (far - near); + + float tx = -(right + left) / (right - left); + float ty = -(top + bottom) / (top - bottom); + float tz = -(far + near) / (far - near); + + projection.set(x, 0, 0, tx, + 0, y, 0, ty, + 0, 0, z, tz, + 0, 0, 0, 1); + } + + + /** + * Calls perspective() with Processing's standard coordinate projection. + *

+ * Projection functions: + *

+ * Each of these three functions completely replaces the projection + * matrix with a new one. They can be called inside setup(), and their + * effects will be felt inside draw(). At the top of draw(), the projection + * matrix is not reset. Therefore the last projection function to be + * called always dominates. On resize, the default projection is always + * established, which has perspective. + *

+ * This behavior is pretty much familiar from OpenGL, except where + * functions replace matrices, rather than multiplying against the + * previous. + *

+ */ + public void perspective() { + perspective(cameraFOV, cameraAspect, cameraNear, cameraFar); + } + + + /** + * Similar to gluPerspective(). Implementation based on Mesa's glu.c + */ + public void perspective(float fov, float aspect, float zNear, float zFar) { + //float ymax = zNear * tan(fovy * PI / 360.0f); + float ymax = zNear * tan(fov / 2.0f); + float ymin = -ymax; + + float xmin = ymin * aspect; + float xmax = ymax * aspect; + + frustum(xmin, xmax, ymin, ymax, zNear, zFar); + } + + + /** + * Same as glFrustum(), except that it wipes out (rather than + * multiplies against) the current perspective matrix. + *

+ * Implementation based on the explanation in the OpenGL blue book. + */ + public void frustum(float left, float right, float bottom, + float top, float znear, float zfar) { + + //if (hints[ENABLE_ACCURATE_TEXTURES]){ + //These vars are only needed if accurate textures are used, however, + //there is the possibility that accurate texturing will only be turned + //on after the perspective matrix has already been set, so we might as + //well store these no matter what since it's not much overhead. + leftScreen = left; + rightScreen = right; + bottomScreen = bottom; + topScreen = top; + nearPlane = znear; + //} + + //System.out.println(projection); + projection.set((2*znear)/(right-left), 0, (right+left)/(right-left), 0, + 0, (2*znear)/(top-bottom), (top+bottom)/(top-bottom), 0, + 0, 0, -(zfar+znear)/(zfar-znear),-(2*zfar*znear)/(zfar-znear), + 0, 0, -1, 0); + } + + + /** + * Print the current projection matrix. + */ + public void printProjection() { + projection.print(); + } + + + /* + * This function checks if the modelview matrix is set up to likely be + * drawing in 2D. It merely checks if the non-translational piece of the + * matrix is unity. If this is to be used, it should be coupled with a + * check that the raw vertex coordinates lie in the z=0 plane. + * Mainly useful for applying sub-pixel shifts to avoid 2d artifacts + * in the screen plane. + * Added by ewjordan 6/13/07 + * + * TODO need to invert the logic here so that we can simply return + * the value, rather than calculating true/false and returning it. + */ + private boolean drawing2D() { + if (modelview.m00 != 1.0f || + modelview.m11 != 1.0f || + modelview.m22 != 1.0f || // check scale + modelview.m01 != 0.0f || + modelview.m02 != 0.0f || // check rotational pieces + modelview.m10 != 0.0f || + modelview.m12 != 0.0f || + modelview.m20 != 0.0f || + modelview.m21 != 0.0f || + !((camera.m23-modelview.m23) <= EPSILON && + (camera.m23-modelview.m23) >= -EPSILON)) { // check for z-translation + // Something about the modelview matrix indicates 3d drawing + // (or rotated 2d, in which case 2d subpixel fixes probably aren't needed) + return false; + } else { + //The matrix is mapping z=0 vertices to the screen plane, + // which means it's likely that 2D drawing is happening. + return true; + } + } + + + + ////////////////////////////////////////////////////////////// + + // SCREEN AND OBJECT COORDINATES + + + public float screenX(float x, float y) { + return screenX(x, y, 0); + } + + + public float screenY(float x, float y) { + return screenY(x, y, 0); + } + + + public float screenX(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + + float ox = + projection.m00*ax + projection.m01*ay + + projection.m02*az + projection.m03*aw; + float ow = + projection.m30*ax + projection.m31*ay + + projection.m32*az + projection.m33*aw; + + if (ow != 0) ox /= ow; + return width * (1 + ox) / 2.0f; + } + + + public float screenY(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + + float oy = + projection.m10*ax + projection.m11*ay + + projection.m12*az + projection.m13*aw; + float ow = + projection.m30*ax + projection.m31*ay + + projection.m32*az + projection.m33*aw; + + if (ow != 0) oy /= ow; + return height * (1 + oy) / 2.0f; + } + + + public float screenZ(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + + float oz = + projection.m20*ax + projection.m21*ay + + projection.m22*az + projection.m23*aw; + float ow = + projection.m30*ax + projection.m31*ay + + projection.m32*az + projection.m33*aw; + + if (ow != 0) oz /= ow; + return (oz + 1) / 2.0f; + } + + + public float modelX(float x, float y, float z) { + float ax = + cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03; + float ay = + cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13; + float az = + cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23; + float aw = + cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33; + + float ox = + modelview.m00*ax + modelview.m01*ay + + modelview.m02*az + modelview.m03*aw; + float ow = + modelview.m30*ax + modelview.m31*ay + + modelview.m32*az + modelview.m33*aw; + + return (ow != 0) ? ox / ow : ox; + } + + + public float modelY(float x, float y, float z) { + float ax = + cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03; + float ay = + cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13; + float az = + cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23; + float aw = + cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33; + + float oy = + modelview.m10*ax + modelview.m11*ay + + modelview.m12*az + modelview.m13*aw; + float ow = + modelview.m30*ax + modelview.m31*ay + + modelview.m32*az + modelview.m33*aw; + + return (ow != 0) ? oy / ow : oy; + } + + + public float modelZ(float x, float y, float z) { + float ax = + cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03; + float ay = + cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13; + float az = + cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23; + float aw = + cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33; + + float oz = + modelview.m20*ax + modelview.m21*ay + + modelview.m22*az + modelview.m23*aw; + float ow = + modelview.m30*ax + modelview.m31*ay + + modelview.m32*az + modelview.m33*aw; + + return (ow != 0) ? oz / ow : oz; + } + + + + ////////////////////////////////////////////////////////////// + + + // strokeWeight() doesn't really work properly either, + // but that will be dealt with in some other way. + + + public void strokeJoin(int join) { + String msg = "strokeJoin() not available with P3D"; + throw new RuntimeException(msg); + } + + + public void strokeCap(int cap) { + String msg = "strokeCap() not available with P3D"; + throw new RuntimeException(msg); + } + + + + ////////////////////////////////////////////////////////////// + + + protected void fillFromCalc() { + super.fillFromCalc(); + ambientFromCalc(); + } + + + ////////////////////////////////////////////////////////////// + + + public void ambient(int rgb) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above + ambient((float) rgb); + + } else { + colorCalcARGB(rgb, colorModeA); + ambientFromCalc(); + } + } + + + public void ambient(float gray) { + colorCalc(gray); + ambientFromCalc(); + } + + + public void ambient(float x, float y, float z) { + colorCalc(x, y, z); + ambientFromCalc(); + } + + + protected void ambientFromCalc() { + ambientR = calcR; + ambientG = calcG; + ambientB = calcB; + } + + + ////////////////////////////////////////////////////////////// + + + public void specular(int rgb) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above + specular((float) rgb); + + } else { + colorCalcARGB(rgb, colorModeA); + specularFromCalc(); + } + } + + + public void specular(float gray) { + colorCalc(gray); + specularFromCalc(); + } + + + public void specular(float gray, float alpha) { + colorCalc(gray, alpha); + specularFromCalc(); + } + + + public void specular(float x, float y, float z) { + colorCalc(x, y, z); + specularFromCalc(); + } + + + public void specular(float x, float y, float z, float a) { + colorCalc(x, y, z, a); + specularFromCalc(); + } + + + protected void specularFromCalc() { + specularR = calcR; + specularG = calcG; + specularB = calcB; + specularA = calcA; + //specularRi = calcRi; + //specularGi = calcGi; + //specularBi = calcBi; + //specularAi = calcAi; + } + + + public void shininess(float shine) { + shininess = shine; + } + + + ////////////////////////////////////////////////////////////// + + + public void emissive(int rgb) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above + emissive((float) rgb); + + } else { + colorCalcARGB(rgb, colorModeA); + emissiveFromCalc(); + } + } + + + public void emissive(float gray) { + colorCalc(gray); + emissiveFromCalc(); + } + + + public void emissive(float x, float y, float z) { + colorCalc(x, y, z); + emissiveFromCalc(); + } + + + protected void emissiveFromCalc() { + emissiveR = calcR; + emissiveG = calcG; + emissiveB = calcB; + //emissiveRi = calcRi; + //emissiveGi = calcGi; + //emissiveBi = calcBi; + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Sets up an ambient and directional light. + *

+   * The Lighting Skinny:
+   *
+   * The way lighting works is complicated enough that it's worth
+   * producing a document to describe it. Lighting calculations proceed
+   * pretty much exactly as described in the OpenGL red book.
+   *
+   * Light-affecting material properties:
+   *
+   *   AMBIENT COLOR
+   *   - multiplies by light's ambient component
+   *   - for believability this should match diffuse color
+   *
+   *   DIFFUSE COLOR
+   *   - multiplies by light's diffuse component
+   *
+   *   SPECULAR COLOR
+   *   - multiplies by light's specular component
+   *   - usually less colored than diffuse/ambient
+   *
+   *   SHININESS
+   *   - the concentration of specular effect
+   *   - this should be set pretty high (20-50) to see really
+   *     noticeable specularity
+   *
+   *   EMISSIVE COLOR
+   *   - constant additive color effect
+   *
+   * Light types:
+   *
+   *   AMBIENT
+   *   - one color
+   *   - no specular color
+   *   - no direction
+   *   - may have falloff (constant, linear, and quadratic)
+   *   - may have position (which matters in non-constant falloff case)
+   *   - multiplies by a material's ambient reflection
+   *
+   *   DIRECTIONAL
+   *   - has diffuse color
+   *   - has specular color
+   *   - has direction
+   *   - no position
+   *   - no falloff
+   *   - multiplies by a material's diffuse and specular reflections
+   *
+   *   POINT
+   *   - has diffuse color
+   *   - has specular color
+   *   - has position
+   *   - no direction
+   *   - may have falloff (constant, linear, and quadratic)
+   *   - multiplies by a material's diffuse and specular reflections
+   *
+   *   SPOT
+   *   - has diffuse color
+   *   - has specular color
+   *   - has position
+   *   - has direction
+   *   - has cone angle (set to half the total cone angle)
+   *   - has concentration value
+   *   - may have falloff (constant, linear, and quadratic)
+   *   - multiplies by a material's diffuse and specular reflections
+   *
+   * Normal modes:
+   *
+   * All of the primitives (rect, box, sphere, etc.) have their normals
+   * set nicely. During beginShape/endShape normals can be set by the user.
+   *
+   *   AUTO-NORMAL
+   *   - if no normal is set during the shape, we are in auto-normal mode
+   *   - auto-normal calculates one normal per triangle (face-normal mode)
+   *
+   *   SHAPE-NORMAL
+   *   - if one normal is set during the shape, it will be used for
+   *     all vertices
+   *
+   *   VERTEX-NORMAL
+   *   - if multiple normals are set, each normal applies to
+   *     subsequent vertices
+   *   - (except for the first one, which applies to previous
+   *     and subsequent vertices)
+   *
+   * Efficiency consequences:
+   *
+   *   There is a major efficiency consequence of position-dependent
+   *   lighting calculations per vertex. (See below for determining
+   *   whether lighting is vertex position-dependent.) If there is no
+   *   position dependency then the only factors that affect the lighting
+   *   contribution per vertex are its colors and its normal.
+   *   There is a major efficiency win if
+   *
+   *   1) lighting is not position dependent
+   *   2) we are in AUTO-NORMAL or SHAPE-NORMAL mode
+   *
+   *   because then we can calculate one lighting contribution per shape
+   *   (SHAPE-NORMAL) or per triangle (AUTO-NORMAL) and simply multiply it
+   *   into the vertex colors. The converse is our worst-case performance when
+   *
+   *   1) lighting is position dependent
+   *   2) we are in AUTO-NORMAL mode
+   *
+   *   because then we must calculate lighting per-face * per-vertex.
+   *   Each vertex has a different lighting contribution per face in
+   *   which it appears. Yuck.
+   *
+   * Determining vertex position dependency:
+   *
+   *   If any of the following factors are TRUE then lighting is
+   *   vertex position dependent:
+   *
+   *   1) Any lights uses non-constant falloff
+   *   2) There are any point or spot lights
+   *   3) There is a light with specular color AND there is a
+   *      material with specular color
+   *
+   * So worth noting is that default lighting (a no-falloff ambient
+   * and a directional without specularity) is not position-dependent.
+   * We should capitalize.
+   *
+   * Simon Greenwold, April 2005
+   * 
+ */ + public void lights() { + // need to make sure colorMode is RGB 255 here + int colorModeSaved = colorMode; + colorMode = RGB; + + lightFalloff(1, 0, 0); + lightSpecular(0, 0, 0); + + ambientLight(colorModeX * 0.5f, + colorModeY * 0.5f, + colorModeZ * 0.5f); + directionalLight(colorModeX * 0.5f, + colorModeY * 0.5f, + colorModeZ * 0.5f, + 0, 0, -1); + + colorMode = colorModeSaved; + + lightingDependsOnVertexPosition = false; + } + + + /** + * Add an ambient light based on the current color mode. + */ + public void ambientLight(float r, float g, float b) { + ambientLight(r, g, b, 0, 0, 0); + } + + + /** + * Add an ambient light based on the current color mode. + * This version includes an (x, y, z) position for situations + * where the falloff distance is used. + */ + public void ambientLight(float r, float g, float b, + float x, float y, float z) { + if (lightCount == MAX_LIGHTS) { + throw new RuntimeException("can only create " + MAX_LIGHTS + " lights"); + } + colorCalc(r, g, b); + lightDiffuse[lightCount][0] = calcR; + lightDiffuse[lightCount][1] = calcG; + lightDiffuse[lightCount][2] = calcB; + + lightType[lightCount] = AMBIENT; + lightFalloffConstant[lightCount] = currentLightFalloffConstant; + lightFalloffLinear[lightCount] = currentLightFalloffLinear; + lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic; + lightPosition(lightCount, x, y, z); + lightCount++; + //return lightCount-1; + } + + + public void directionalLight(float r, float g, float b, + float nx, float ny, float nz) { + if (lightCount == MAX_LIGHTS) { + throw new RuntimeException("can only create " + MAX_LIGHTS + " lights"); + } + colorCalc(r, g, b); + lightDiffuse[lightCount][0] = calcR; + lightDiffuse[lightCount][1] = calcG; + lightDiffuse[lightCount][2] = calcB; + + lightType[lightCount] = DIRECTIONAL; + lightFalloffConstant[lightCount] = currentLightFalloffConstant; + lightFalloffLinear[lightCount] = currentLightFalloffLinear; + lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic; + lightSpecular[lightCount][0] = currentLightSpecular[0]; + lightSpecular[lightCount][1] = currentLightSpecular[1]; + lightSpecular[lightCount][2] = currentLightSpecular[2]; + lightDirection(lightCount, nx, ny, nz); + lightCount++; + } + + + public void pointLight(float r, float g, float b, + float x, float y, float z) { + if (lightCount == MAX_LIGHTS) { + throw new RuntimeException("can only create " + MAX_LIGHTS + " lights"); + } + colorCalc(r, g, b); + lightDiffuse[lightCount][0] = calcR; + lightDiffuse[lightCount][1] = calcG; + lightDiffuse[lightCount][2] = calcB; + + lightType[lightCount] = POINT; + lightFalloffConstant[lightCount] = currentLightFalloffConstant; + lightFalloffLinear[lightCount] = currentLightFalloffLinear; + lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic; + lightSpecular[lightCount][0] = currentLightSpecular[0]; + lightSpecular[lightCount][1] = currentLightSpecular[1]; + lightSpecular[lightCount][2] = currentLightSpecular[2]; + lightPosition(lightCount, x, y, z); + lightCount++; + + lightingDependsOnVertexPosition = true; + } + + + public void spotLight(float r, float g, float b, + float x, float y, float z, + float nx, float ny, float nz, + float angle, float concentration) { + if (lightCount == MAX_LIGHTS) { + throw new RuntimeException("can only create " + MAX_LIGHTS + " lights"); + } + colorCalc(r, g, b); + lightDiffuse[lightCount][0] = calcR; + lightDiffuse[lightCount][1] = calcG; + lightDiffuse[lightCount][2] = calcB; + + lightType[lightCount] = SPOT; + lightFalloffConstant[lightCount] = currentLightFalloffConstant; + lightFalloffLinear[lightCount] = currentLightFalloffLinear; + lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic; + lightSpecular[lightCount][0] = currentLightSpecular[0]; + lightSpecular[lightCount][1] = currentLightSpecular[1]; + lightSpecular[lightCount][2] = currentLightSpecular[2]; + lightPosition(lightCount, x, y, z); + lightDirection(lightCount, nx, ny, nz); + lightSpotAngle[lightCount] = angle; + lightSpotAngleCos[lightCount] = max(0, cos(angle)); + lightSpotConcentration[lightCount] = concentration; + lightCount++; + + lightingDependsOnVertexPosition = true; + } + + + /** + * Set the light falloff rates for the last light that was created. + * Default is lightFalloff(1, 0, 0). + */ + public void lightFalloff(float constant, float linear, float quadratic) { + currentLightFalloffConstant = constant; + currentLightFalloffLinear = linear; + currentLightFalloffQuadratic = quadratic; + + lightingDependsOnVertexPosition = true; + } + + + /** + * Set the specular color of the last light created. + */ + public void lightSpecular(float x, float y, float z) { + colorCalc(x, y, z); + currentLightSpecular[0] = calcR; + currentLightSpecular[1] = calcG; + currentLightSpecular[2] = calcB; + + lightingDependsOnVertexPosition = true; + } + + + /** + * internal function to set the light position + * based on the current modelview matrix. + */ + protected void lightPosition(int num, float x, float y, float z) { + lightPosition[num][0] = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + lightPosition[num][1] = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + lightPosition[num][2] = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + } + + + /** + * internal function to set the light direction + * based on the current modelview matrix. + */ + protected void lightDirection(int num, float x, float y, float z) { + // Multiply by inverse transpose. + lightNormal[num][0] = + modelviewInv.m00*x + modelviewInv.m10*y + + modelviewInv.m20*z + modelviewInv.m30; + lightNormal[num][1] = + modelviewInv.m01*x + modelviewInv.m11*y + + modelviewInv.m21*z + modelviewInv.m31; + lightNormal[num][2] = + modelviewInv.m02*x + modelviewInv.m12*y + + modelviewInv.m22*z + modelviewInv.m32; + + float n = mag(lightNormal[num]); + if (n == 0 || n == 1) return; + + lightNormal[num][0] /= n; + lightNormal[num][1] /= n; + lightNormal[num][2] /= n; + } + + + + ////////////////////////////////////////////////////////////// + + // BACKGROUND + + + /** + * Takes an RGB or RGBA image and sets it as the background. + *

+ * Note that even if the image is set as RGB, the high 8 bits of + * each pixel must be set (0xFF000000), because the image data will + * be copied directly to the screen. + *

+ * Also clears out the zbuffer and stencil buffer if they exist. + */ + public void background(PImage image) { + super.background(image); + + for (int i = 0; i < pixelCount; i++) { + zbuffer[i] = Float.MAX_VALUE; + //stencil[i] = 0; + } + } + + + /** + * Clear pixel buffer. With P3D and OPENGL, this also clears the zbuffer. + * Stencil buffer should also be cleared, but for now is ignored in P3D. + */ + protected void clear() { + //System.out.println("PGraphics3.clear(" + + // PApplet.hex(backgroundColor) + ")"); + for (int i = 0; i < pixelCount; i++) { + pixels[i] = backgroundColor; + zbuffer[i] = Float.MAX_VALUE; + //stencil[i] = 0; + } + } + + + + ////////////////////////////////////////////////////////////// + + // SMOOTH (not available, throws error) + + // although should this bother throwing an error? + // could be a pain in the ass when trying to debug with opengl + + + public void smooth() { + String msg = "smooth() not available with P3D"; + throw new RuntimeException(msg); + } + + + public void noSmooth() { + String msg = "noSmooth() not available with P3D"; + throw new RuntimeException(msg); + } + + + + ////////////////////////////////////////////////////////////// + + // MATH (internal use only) + + + /* + private final float mag(float a, float b) { + return (float)Math.sqrt(a*a + b*b); + } + */ + + private final float mag(float a, float b, float c) { + return (float)Math.sqrt(a*a + b*b + c*c); + } + + private final float mag(float abc[]) { + return (float)Math.sqrt(abc[0]*abc[0] + abc[1]*abc[1] + abc[2]*abc[2]); + } + + private final float min(float a, float b) { + return (a < b) ? a : b; + } + + private final float max(float a, float b) { + return (a > b) ? a : b; + } + + /* + private final float max(float a, float b, float c) { + return Math.max(a, Math.max(b, c)); + } + + private final float sq(float a) { + return a*a; + } + */ + + private final float pow(float a, float b) { + return (float)Math.pow(a, b); + } + + private final float abs(float a) { + return (a < 0) ? -a : a; + } + + /* + private final float sin(float angle) { + return (float)Math.sin(angle); + } + */ + + private final float cos(float angle) { + return (float)Math.cos(angle); + } + + private final float tan(float angle) { + return (float)Math.tan(angle); + } + + private float dot(float ax, float ay, float az, + float bx, float by, float bz) { + return ax * bx + ay * by + az * bz; + } +} + diff --git a/core/PGraphicsJava2D.java b/core/PGraphicsJava2D.java new file mode 100644 index 000000000..5766addce --- /dev/null +++ b/core/PGraphicsJava2D.java @@ -0,0 +1,1381 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2005-06 Ben Fry and Casey Reas + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.*; + + +/** + * Subclass for PGraphics that implements the graphics API + * in Java 1.3+ using Java 2D. + * + *

Pixel operations too slow? As of release 0085 (the first beta), + * the default renderer uses Java2D. It's more accurate than the renderer + * used in alpha releases of Processing (it handles stroke caps and joins, + * and has better polygon tessellation), but it's super slow for handling + * pixels. At least until we get a chance to get the old 2D renderer + * (now called P2D) working in a similar fashion, you can use + * size(w, h, P3D) instead of size(w, h) which will + * be faster for general pixel flipping madness.

+ * + *

To get access to the Java 2D "Graphics2D" object for the default + * renderer, use: + *

Graphics2D g2 = ((PGraphicsJava2D)g).g2;
+ * This will let you do Java 2D stuff directly, but is not supported in + * any way shape or form. Which just means "have fun, but don't complain + * if it breaks."

+ */ +public class PGraphicsJava2D extends PGraphics { + + public Graphics2D g2; + GeneralPath gpath; + + int transformCount; + AffineTransform transformStack[] = + new AffineTransform[MATRIX_STACK_DEPTH]; + double transform[] = new double[6]; + + Line2D.Float line = new Line2D.Float(); + Ellipse2D.Float ellipse = new Ellipse2D.Float(); + Rectangle2D.Float rect = new Rectangle2D.Float(); + Arc2D.Float arc = new Arc2D.Float(); + + protected Color tintColorObject; + + protected Color fillColorObject; + public boolean fillGradient; + public Paint fillGradientObject; + + protected Color strokeColorObject; + public boolean strokeGradient; + public Paint strokeGradientObject; + + + + ////////////////////////////////////////////////////////////// + + // INTERNAL + + + /** + * Constructor for the PGraphicsJava object. + * This prototype only exists because of annoying + * java compilers, and should not be used. + */ + //public PGraphicsJava2D() { } + + + /** + * Constructor for the PGraphics object. Use this to ensure that + * the defaults get set properly. In a subclass, use this(w, h) + * as the first line of a subclass' constructor to properly set + * the internal fields and defaults. + * + * @param iwidth viewport width + * @param iheight viewport height + */ + public PGraphicsJava2D(int iwidth, int iheight, PApplet parent) { + super(iwidth, iheight, parent); + //resize(iwidth, iheight); + } + + + /** + * Called in repsonse to a resize event, handles setting the + * new width and height internally, as well as re-allocating + * the pixel buffer for the new size. + * + * Note that this will nuke any cameraMode() settings. + */ + public void resize(int iwidth, int iheight) { // ignore + //System.out.println("resize " + iwidth + " " + iheight); + insideDrawWait(); + insideResize = true; + + width = iwidth; + height = iheight; + width1 = width - 1; + height1 = height - 1; + + allocate(); + + // ok to draw again + insideResize = false; + } + + + // broken out because of subclassing for opengl + protected void allocate() { + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + g2 = (Graphics2D) image.getGraphics(); + // can't un-set this because this may be only a resize (Bug #463) + //defaultsInited = false; + } + + + + ////////////////////////////////////////////////////////////// + + // FRAME + + + public void beginDraw() { + insideResizeWait(); + insideDraw = true; + + // need to call defaults(), but can only be done when it's ok + // to draw (i.e. for opengl, no drawing can be done outside + // beginDraw/endDraw). + if (!defaultsInited) defaults(); + + resetMatrix(); // reset model matrix + + // reset vertices + vertexCount = 0; + } + + + public void endDraw() { + // hm, mark pixels as changed, because this will instantly do a full + // copy of all the pixels to the surface.. so that's kind of a mess. + //updatePixels(); + + if (!mainDrawingSurface) { + loadPixels(); + } + modified = true; + insideDraw = false; + } + + + + ////////////////////////////////////////////////////////////// + + // SHAPES + + + public void beginShape(int kind) { + //super.beginShape(kind); + shape = kind; + vertexCount = 0; + splineVertexCount = 0; + + // set gpath to null, because when mixing curves and straight + // lines, vertexCount will be set back to zero, so vertexCount == 1 + // is no longer a good indicator of whether the shape is new. + // this way, just check to see if gpath is null, and if it isn't + // then just use it to continue the shape. + gpath = null; + } + + + public void textureMode(int mode) { + unavailableError("textureMode(mode)"); + } + + + public void texture(PImage image) { + unavailableError("texture(image)"); + } + + + public void vertex(float x, float y) { + splineVertexCount = 0; + //float vertex[]; + + if (vertexCount == vertices.length) { + float temp[][] = new float[vertexCount<<1][VERTEX_FIELD_COUNT]; + System.arraycopy(vertices, 0, temp, 0, vertexCount); + vertices = temp; + //message(CHATTER, "allocating more vertices " + vertices.length); + } + // not everyone needs this, but just easier to store rather + // than adding another moving part to the code... + vertices[vertexCount][MX] = x; + vertices[vertexCount][MY] = y; + vertexCount++; + + switch (shape) { + + case POINTS: + point(x, y); + break; + + case LINES: + if ((vertexCount % 2) == 0) { + line(vertices[vertexCount-2][MX], + vertices[vertexCount-2][MY], x, y); + } + break; + +/* + case LINE_STRIP: + case LINE_LOOP: + if (gpath == null) { + gpath = new GeneralPath(); + gpath.moveTo(x, y); + } else { + gpath.lineTo(x, y); + } + break; +*/ + + case TRIANGLES: + if ((vertexCount % 3) == 0) { + triangle(vertices[vertexCount - 3][MX], + vertices[vertexCount - 3][MY], + vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY], + x, y); + } + break; + + case TRIANGLE_STRIP: + if (vertexCount >= 3) { + triangle(vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY], + vertices[vertexCount - 1][MX], + vertices[vertexCount - 1][MY], + vertices[vertexCount - 3][MX], + vertices[vertexCount - 3][MY]); + } + break; + + case TRIANGLE_FAN: + if (vertexCount == 3) { + triangle(vertices[0][MX], vertices[0][MY], + vertices[1][MX], vertices[1][MY], + x, y); + } else if (vertexCount > 3) { + gpath = new GeneralPath(); + // when vertexCount > 3, draw an un-closed triangle + // for indices 0 (center), previous, current + gpath.moveTo(vertices[0][MX], + vertices[0][MY]); + gpath.lineTo(vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY]); + gpath.lineTo(x, y); + draw_shape(gpath); + } + break; + + case QUADS: + if ((vertexCount % 4) == 0) { + quad(vertices[vertexCount - 4][MX], + vertices[vertexCount - 4][MY], + vertices[vertexCount - 3][MX], + vertices[vertexCount - 3][MY], + vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY], + x, y); + } + break; + + case QUAD_STRIP: + // 0---2---4 + // | | | + // 1---3---5 + if ((vertexCount >= 4) && ((vertexCount % 2) == 0)) { + quad(vertices[vertexCount - 4][MX], + vertices[vertexCount - 4][MY], + vertices[vertexCount - 2][MX], + vertices[vertexCount - 2][MY], + x, y, + vertices[vertexCount - 3][MX], + vertices[vertexCount - 3][MY]); + } + break; + + case POLYGON: + if (gpath == null) { + gpath = new GeneralPath(); + gpath.moveTo(x, y); + } else if (breakShape) { + gpath.moveTo(x, y); + breakShape = false; + } else { + gpath.lineTo(x, y); + } + break; + } + } + + + public void vertex(float x, float y, float u, float v) { + unavailableError("vertex(x, y, u, v"); + } + + + public void vertex(float x, float y, float z) { + depthErrorXYZ("vertex"); + } + + + public void vertex(float x, float y, float z, float u, float v) { + depthErrorXYZ("vertex"); + } + + + public void bezierVertex(float x1, float y1, + float x2, float y2, + float x3, float y3) { + if (gpath == null) { + throw new RuntimeException("Must call vertex() at least once " + + "before using bezierVertex()"); + } + + switch (shape) { + //case LINE_LOOP: + //case LINE_STRIP: + case POLYGON: + gpath.curveTo(x1, y1, x2, y2, x3, y3); + break; + + default: + throw new RuntimeException("bezierVertex() can only be used with " + + "LINE_STRIP, LINE_LOOP, or POLYGON"); + } + } + + + float curveX[] = new float[4]; + float curveY[] = new float[4]; + + public void curveVertex(float x, float y) { + //if ((shape != LINE_LOOP) && (shape != LINE_STRIP) && (shape != POLYGON)) { + if (shape != POLYGON) { + throw new RuntimeException("curveVertex() can only be used with " + + "POLYGON shapes"); + //"LINE_LOOP, LINE_STRIP, and POLYGON shapes"); + } + + if (!curve_inited) curve_init(); + vertexCount = 0; + + if (splineVertices == null) { + splineVertices = new float[DEFAULT_SPLINE_VERTICES][VERTEX_FIELD_COUNT]; + } + + // if more than 128 points, shift everything back to the beginning + if (splineVertexCount == DEFAULT_SPLINE_VERTICES) { + System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES - 3], 0, + splineVertices[0], 0, VERTEX_FIELD_COUNT); + System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES - 2], 0, + splineVertices[1], 0, VERTEX_FIELD_COUNT); + System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES - 1], 0, + splineVertices[2], 0, VERTEX_FIELD_COUNT); + splineVertexCount = 3; + } + + // this new guy will be the fourth point (or higher), + // which means it's time to draw segments of the curve + if (splineVertexCount >= 3) { + curveX[0] = splineVertices[splineVertexCount-3][MX]; + curveY[0] = splineVertices[splineVertexCount-3][MY]; + + curveX[1] = splineVertices[splineVertexCount-2][MX]; + curveY[1] = splineVertices[splineVertexCount-2][MY]; + + curveX[2] = splineVertices[splineVertexCount-1][MX]; + curveY[2] = splineVertices[splineVertexCount-1][MY]; + + curveX[3] = x; + curveY[3] = y; + + curveToBezierMatrix.mult(curveX, curveX); + curveToBezierMatrix.mult(curveY, curveY); + + // since the paths are continuous, + // only the first point needs the actual moveto + if (gpath == null) { + gpath = new GeneralPath(); + gpath.moveTo(curveX[0], curveY[0]); + } + + gpath.curveTo(curveX[1], curveY[1], + curveX[2], curveY[2], + curveX[3], curveY[3]); + } + + // add the current point to the list + splineVertices[splineVertexCount][MX] = x; + splineVertices[splineVertexCount][MY] = y; + splineVertexCount++; + } + + + boolean breakShape; + public void breakShape() { + breakShape = true; + } + + + public void endShape(int mode) { + if (gpath != null) { // make sure something has been drawn + if (shape == POLYGON) { + if (mode == CLOSE) { + gpath.closePath(); + } + draw_shape(gpath); + } + } + shape = 0; + } + + + + ////////////////////////////////////////////////////////////// + + + /* + protected void fillGradient(Paint paint) { + fillGradient = true; + fillGradientObject = paint; + } + + + protected void noFillGradient() { + fillGradient = false; + } + */ + + + ////////////////////////////////////////////////////////////// + + + protected void fill_shape(Shape s) { + if (fillGradient) { + g2.setPaint(fillGradientObject); + g2.fill(s); + } else if (fill) { + g2.setColor(fillColorObject); + g2.fill(s); + } + } + + protected void stroke_shape(Shape s) { + if (strokeGradient) { + g2.setPaint(strokeGradientObject); + g2.draw(s); + } else if (stroke) { + g2.setColor(strokeColorObject); + g2.draw(s); + } + } + + protected void draw_shape(Shape s) { + if (fillGradient) { + g2.setPaint(fillGradientObject); + g2.fill(s); + } else if (fill) { + g2.setColor(fillColorObject); + g2.fill(s); + } + if (strokeGradient) { + g2.setPaint(strokeGradientObject); + g2.draw(s); + } else if (stroke) { + g2.setColor(strokeColorObject); + g2.draw(s); + } + } + + + ////////////////////////////////////////////////////////////// + + + public void point(float x, float y) { + line(x, y, x, y); + } + + + public void line(float x1, float y1, float x2, float y2) { + //graphics.setColor(strokeColorObject); + //graphics.drawLine(x1, y1, x2, y2); + line.setLine(x1, y1, x2, y2); + stroke_shape(line); + } + + + public void triangle(float x1, float y1, float x2, float y2, + float x3, float y3) { + gpath = new GeneralPath(); + gpath.moveTo(x1, y1); + gpath.lineTo(x2, y2); + gpath.lineTo(x3, y3); + gpath.closePath(); + + draw_shape(gpath); + } + + + public void quad(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + GeneralPath gp = new GeneralPath(); + gp.moveTo(x1, y1); + gp.lineTo(x2, y2); + gp.lineTo(x3, y3); + gp.lineTo(x4, y4); + gp.closePath(); + + draw_shape(gp); + } + + + ////////////////////////////////////////////////////////////// + + + protected void rectImpl(float x1, float y1, float x2, float y2) { + rect.setFrame(x1, y1, x2-x1, y2-y1); + draw_shape(rect); + } + + + protected void ellipseImpl(float x, float y, float w, float h) { + ellipse.setFrame(x, y, w, h); + draw_shape(ellipse); + } + + + protected void arcImpl(float x, float y, float w, float h, + float start, float stop) { + // 0 to 90 in java would be 0 to -90 for p5 renderer + // but that won't work, so -90 to 0? + + if (stop - start >= TWO_PI) { + start = 0; + stop = 360; + + } else { + start = -start * RAD_TO_DEG; + stop = -stop * RAD_TO_DEG; + + // ok to do this because already checked for NaN + while (start < 0) { + start += 360; + stop += 360; + } + if (start > stop) { + float temp = start; + start = stop; + stop = temp; + } + } + float span = stop - start; + + // stroke as Arc2D.OPEN, fill as Arc2D.PIE + if (fill) { + //System.out.println("filla"); + arc.setArc(x, y, w, h, start, span, Arc2D.PIE); + fill_shape(arc); + } + if (stroke) { + //System.out.println("strokey"); + arc.setArc(x, y, w, h, start, span, Arc2D.OPEN); + stroke_shape(arc); + } + } + + + ////////////////////////////////////////////////////////////// + + + /** Ignored (not needed) in Java 2D. */ + public void bezierDetail(int detail) { + } + + + /** Ignored (not needed) in Java 2D. */ + public void curveDetail(int detail) { + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Handle renderer-specific image drawing. + */ + protected void imageImpl(PImage who, + float x1, float y1, float x2, float y2, + int u1, int v1, int u2, int v2) { + if (who.cache != null) { + if (!(who.cache instanceof ImageCache)) { + // this cache belongs to another renderer.. fix me later, + // because this is gonna make drawing *really* inefficient + //who.cache = null; + } + } + + if (who.cache == null) { + //System.out.println("making new image cache"); + who.cache = new ImageCache(who); + who.updatePixels(); // mark the whole thing for update + who.modified = true; + } + + ImageCache cash = (ImageCache) who.cache; + // if image previously was tinted, or the color changed + // or the image was tinted, and tint is now disabled + if ((tint && !cash.tinted) || + (tint && (cash.tintedColor != tintColor)) || + (!tint && cash.tinted)) { + // for tint change, mark all pixels as needing update + who.updatePixels(); + } + + if (who.modified) { + cash.update(tint, tintColor); + who.modified = false; + } + + g2.drawImage(((ImageCache) who.cache).image, + (int) x1, (int) y1, (int) x2, (int) y2, + u1, v1, u2, v2, null); + } + + + class ImageCache { + PImage source; + boolean tinted; + int tintedColor; + int tintedPixels[]; + BufferedImage image; + + public ImageCache(PImage source) { + this.source = source; + // even if RGB, set the image type to ARGB, because the + // image may have an alpha value for its tint(). + int type = BufferedImage.TYPE_INT_ARGB; + //System.out.println("making new buffered image"); + image = new BufferedImage(source.width, source.height, type); + } + + // for rev 0124, passing the tintColor in here. the problem is that + // the 'parent' PGraphics object of this inner class may not be + // the same one that's used when drawing. for instance, if this + // is a font used by the main drawing surface, then it's later + // used in an offscreen PGraphics, the tintColor value from the + // original PGraphics will be used. + public void update(boolean tint, int tintColor) { + if (tintedPixels == null) { + //System.out.println("tinted pixels null"); + tintedPixels = new int[source.width * source.height]; + } + + if ((source.format == ARGB) || (source.format == RGB)) { + if (tint) { + // create tintedPixels[] if necessary + //if (tintedPixels == null) { + // tintedPixels = new int[source.width * source.height]; + //} + + int a2 = (tintColor >> 24) & 0xff; + int r2 = (tintColor >> 16) & 0xff; + int g2 = (tintColor >> 8) & 0xff; + int b2 = (tintColor) & 0xff; + + // multiply each of the color components into tintedPixels + // if straight RGB image, don't bother multiplying + // (also avoids problems if high bits not set) + if (source.format == RGB) { + int alpha = a2 << 24; + + for (int i = 0; i < tintedPixels.length; i++) { + int argb1 = source.pixels[i]; + int r1 = (argb1 >> 16) & 0xff; + int g1 = (argb1 >> 8) & 0xff; + int b1 = (argb1) & 0xff; + + tintedPixels[i] = alpha | + (((r2 * r1) & 0xff00) << 8) | + ((g2 * g1) & 0xff00) | + (((b2 * b1) & 0xff00) >> 8); + } + + } else { + for (int i = 0; i < tintedPixels.length; i++) { + int argb1 = source.pixels[i]; + int a1 = (argb1 >> 24) & 0xff; + int r1 = (argb1 >> 16) & 0xff; + int g1 = (argb1 >> 8) & 0xff; + int b1 = (argb1) & 0xff; + + tintedPixels[i] = + (((a2 * a1) & 0xff00) << 16) | + (((r2 * r1) & 0xff00) << 8) | + ((g2 * g1) & 0xff00) | + (((b2 * b1) & 0xff00) >> 8); + } + } + + tinted = true; + tintedColor = tintColor; + + // finally, do a setRGB based on tintedPixels + //image.setRGB(0, 0, source.width, source.height, + // tintedPixels, 0, source.width); + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.setDataElements(0, 0, source.width, source.height, + tintedPixels); + + } else { // no tint + // just do a setRGB like before + // (and we'll just hope that the high bits are set) + //image.setRGB(0, 0, source.width, source.height, + // source.pixels, 0, source.width); + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.setDataElements(0, 0, source.width, source.height, + source.pixels); + } + + } else if (source.format == ALPHA) { + int lowbits = tintColor & 0x00ffffff; + if (((tintColor >> 24) & 0xff) >= 254) { + //PApplet.println(" no alfa " + PApplet.hex(tintColor)); + // no actual alpha to the tint, set the image's alpha + // as the high 8 bits, and use the color as the low 24 bits + for (int i = 0; i < tintedPixels.length; i++) { + // don't bother with the math if value is zero + tintedPixels[i] = (source.pixels[i] == 0) ? + 0 : (source.pixels[i] << 24) | lowbits; + } + + } else { + //PApplet.println(" yes alfa " + PApplet.hex(tintColor)); + // multiply each image alpha by the tint alpha + int alphabits = (tintColor >> 24) & 0xff; + for (int i = 0; i < tintedPixels.length; i++) { + tintedPixels[i] = (source.pixels[i] == 0) ? + 0 : (((alphabits * source.pixels[i]) & 0xFF00) << 16) | lowbits; + } + } + + // mark the pixels for next time + tinted = true; + tintedColor = tintColor; + + // finally, do a setRGB based on tintedPixels + //image.setRGB(0, 0, source.width, source.height, + // tintedPixels, 0, source.width); + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.setDataElements(0, 0, source.width, source.height, tintedPixels); + } + } + } + + + ////////////////////////////////////////////////////////////// + + + public float textAscent() { + if (textFontNative == null) { + return super.textAscent(); + } + return textFontNativeMetrics.getAscent(); + } + + + public float textDescent() { + if (textFontNative == null) { + return super.textDescent(); + } + return textFontNativeMetrics.getDescent(); + } + + + /** + * Same as parent, but override for native version of the font. + *

+ * Also gets called by textFont, so the metrics + * will get recorded properly. + */ + public void textSize(float size) { + // if a native version available, subset this font + if (textFontNative != null) { + textFontNative = textFontNative.deriveFont(size); + g2.setFont(textFontNative); + textFontNativeMetrics = g2.getFontMetrics(textFontNative); + } + + // take care of setting the textSize and textLeading vars + // this has to happen second, because it calls textAscent() + // (which requires the native font metrics to be set) + super.textSize(size); + } + + + protected float textWidthImpl(char buffer[], int start, int stop) { + if (textFontNative == null) { + //System.out.println("native is null"); + return super.textWidthImpl(buffer, start, stop); + } + // maybe should use one of the newer/fancier functions for this? + int length = stop - start; + return textFontNativeMetrics.charsWidth(buffer, start, length); + } + + + protected void textLinePlacedImpl(char buffer[], int start, int stop, + float x, float y) { + if (textFontNative == null) { + super.textLinePlacedImpl(buffer, start, stop, x, y); + return; + } + + /* + // save the current setting for text smoothing. note that this is + // different from the smooth() function, because the font smoothing + // is controlled when the font is created, not now as it's drawn. + // fixed a bug in 0116 that handled this incorrectly. + Object textAntialias = + g2.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING); + + // override the current text smoothing setting based on the font + // (don't change the global smoothing settings) + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + textFont.smooth ? + RenderingHints.VALUE_ANTIALIAS_ON : + RenderingHints.VALUE_ANTIALIAS_OFF); + */ + + Object antialias = + g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + if (antialias == null) { + // if smooth() and noSmooth() not called, this will be null (0120) + antialias = RenderingHints.VALUE_ANTIALIAS_DEFAULT; + } + + // override the current smoothing setting based on the font + // also changes global setting for antialiasing, but this is because it's + // not possible to enable/disable them independently in some situations. + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + textFont.smooth ? + RenderingHints.VALUE_ANTIALIAS_ON : + RenderingHints.VALUE_ANTIALIAS_OFF); + + + g2.setColor(fillColorObject); + // better to use drawString(float, float)? + int length = stop - start; + g2.drawChars(buffer, start, length, (int) (x + 0.5f), (int) (y + 0.5f)); + + // this didn't seem to help the scaling issue + // and creates garbage because of the new temporary object + //java.awt.font.GlyphVector gv = textFontNative.createGlyphVector(g2.getFontRenderContext(), new String(buffer, start, stop)); + //g2.drawGlyphVector(gv, x, y); + + // return to previous smoothing state if it was changed + //g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, textAntialias); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias); + + textX = x + textWidthImpl(buffer, start, stop); + textY = y; + textZ = 0; // this will get set by the caller if non-zero + } + + + ////////////////////////////////////////////////////////////// + + + public void translate(float tx, float ty) { + g2.translate(tx, ty); + } + + + public void rotate(float angle) { + g2.rotate(angle); + } + + + public void scale(float s) { + g2.scale(s, s); + } + + + public void scale(float sx, float sy) { + g2.scale(sx, sy); + } + + + ////////////////////////////////////////////////////////////// + + + public void pushMatrix() { + if (transformCount == transformStack.length) { + throw new RuntimeException("pushMatrix() cannot use push more than " + + transformStack.length + " times"); + } + transformStack[transformCount] = g2.getTransform(); + transformCount++; + } + + + public void popMatrix() { + if (transformCount == 0) { + throw new RuntimeException("missing a popMatrix() " + + "to go with that pushMatrix()"); + } + transformCount--; + g2.setTransform(transformStack[transformCount]); + } + + + public void resetMatrix() { + g2.setTransform(new AffineTransform()); + } + + + public void applyMatrix(float n00, float n01, float n02, + float n10, float n11, float n12) { + g2.transform(new AffineTransform(n00, n10, n01, n11, n02, n12)); + } + + + public void loadMatrix() { + g2.getTransform().getMatrix(transform); + + m00 = (float) transform[0]; + m01 = (float) transform[2]; + m02 = (float) transform[4]; + + m10 = (float) transform[1]; + m11 = (float) transform[3]; + m12 = (float) transform[5]; + } + + + public float screenX(float x, float y) { + loadMatrix(); + return super.screenX(x, y); + //g2.getTransform().getMatrix(transform); + //return (float)transform[0]*x + (float)transform[2]*y + (float)transform[4]; + } + + + public float screenY(float x, float y) { + loadMatrix(); + return super.screenY(x, y); + //g2.getTransform().getMatrix(transform); + //return (float)transform[1]*x + (float)transform[3]*y + (float)transform[5]; + } + + + ////////////////////////////////////////////////////////////// + + + protected void tintFromCalc() { + super.tintFromCalc(); + // TODO actually implement tinted images + tintColorObject = new Color(tintColor, true); + } + + protected void fillFromCalc() { + super.fillFromCalc(); + fillColorObject = new Color(fillColor, true); + fillGradient = false; + } + + protected void strokeFromCalc() { + super.strokeFromCalc(); + strokeColorObject = new Color(strokeColor, true); + strokeGradient = false; + } + + + ////////////////////////////////////////////////////////////// + + + public void strokeWeight(float weight) { + super.strokeWeight(weight); + set_stroke(); + } + + + public void strokeJoin(int join) { + super.strokeJoin(join); + set_stroke(); + } + + + public void strokeCap(int cap) { + super.strokeCap(cap); + set_stroke(); + } + + + protected void set_stroke() { + int cap = BasicStroke.CAP_BUTT; + if (strokeCap == ROUND) { + cap = BasicStroke.CAP_ROUND; + } else if (strokeCap == PROJECT) { + cap = BasicStroke.CAP_SQUARE; + } + + int join = BasicStroke.JOIN_BEVEL; + if (strokeJoin == MITER) { + join = BasicStroke.JOIN_MITER; + } else if (strokeJoin == ROUND) { + join = BasicStroke.JOIN_ROUND; + } + + g2.setStroke(new BasicStroke(strokeWeight, cap, join)); + } + + + ////////////////////////////////////////////////////////////// + + + public void background(PImage image) { + if ((image.width != width) || (image.height != height)) { + throw new RuntimeException("background image must be " + + "the same size as your application"); + } + if ((image.format != RGB) && (image.format != ARGB)) { + throw new RuntimeException("background images should be RGB or ARGB"); + } + // draw the image to screen without any transformations + set(0, 0, image); + } + + + int[] clearPixels; + + public void clear() { + // the only way to properly clear the screen is to re-allocate + if (backgroundAlpha) { + // clearRect() doesn't work because it just makes everything black. + // instead, just wipe out the canvas to its transparent original + //allocate(); + + // allocate also won't work, because all the settings + // (like smooth) will be completely reset. + // Instead, create a small array that can be used to set the pixels + // several times. Using a single-pixel line of length 'width' is a + // tradeoff between speed (setting each pixel individually is too slow) + // and memory (an array for width*height would waste lots of memory + // if it stayed resident, and would terrify the gc if it were + // re-created on each trip to background(). + WritableRaster raster = ((BufferedImage) image).getRaster(); + if ((clearPixels == null) || (clearPixels.length < width)) { + clearPixels = new int[width]; + } + for (int i = 0; i < width; i++) { + clearPixels[i] = backgroundColor; + } + for (int i = 0; i < height; i++) { + raster.setDataElements(0, i, width, 1, clearPixels); + } + } else { + // in case people do transformations before background(), + // need to handle this with a push/reset/pop + pushMatrix(); + resetMatrix(); + g2.setColor(new Color(backgroundColor, backgroundAlpha)); + g2.fillRect(0, 0, width, height); + popMatrix(); + } + } + + + + ////////////////////////////////////////////////////////////// + + // FROM PIMAGE + + + public void smooth() { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BICUBIC); + } + + + public void noSmooth() { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_OFF); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + } + + + + ////////////////////////////////////////////////////////////// + + + public void beginRaw(PGraphics recorderRaw) { + throw new RuntimeException("beginRaw() not available with this renderer"); + } + + + public void endRaw() { + } + + + ////////////////////////////////////////////////////////////// + + + public void loadPixels() { + if ((pixels == null) || (pixels.length != width * height)) { + pixels = new int[width * height]; + } + //((BufferedImage) image).getRGB(0, 0, width, height, pixels, 0, width); + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.getDataElements(0, 0, width, height, pixels); + } + + + /** + * Update the pixels[] buffer to the PGraphics image. + *

+ * Unlike in PImage, where updatePixels() only requests that the + * update happens, in PGraphicsJava2D, this will happen immediately. + */ + public void updatePixels() { + //updatePixels(0, 0, width, height); + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.setDataElements(0, 0, width, height, pixels); + } + + + /** + * Update the pixels[] buffer to the PGraphics image. + *

+ * Unlike in PImage, where updatePixels() only requests that the + * update happens, in PGraphicsJava2D, this will happen immediately. + */ + public void updatePixels(int x, int y, int c, int d) { + if ((x == 0) && (y == 0) && (c == width) && (d == height)) { + updatePixels(); + } else { + throw new RuntimeException("updatePixels(x, y, c, d) not implemented"); + } + /* + ((BufferedImage) image).setRGB(x, y, + (imageMode == CORNER) ? c : (c - x), + (imageMode == CORNER) ? d : (d - y), + pixels, 0, width); + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.setDataElements(x, y, + (imageMode == CORNER) ? c : (c - x), + (imageMode == CORNER) ? d : (d - y), + pixels); + */ + } + + + ////////////////////////////////////////////////////////////// + + + static int getset[] = new int[1]; + + + public int get(int x, int y) { + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0; + //return ((BufferedImage) image).getRGB(x, y); + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.getDataElements(x, y, getset); + return getset[0]; + } + + + public PImage get(int x, int y, int w, int h) { + if (imageMode == CORNERS) { // if CORNER, do nothing + // w/h are x2/y2 in this case, bring em down to size + w = (w - x); + h = (h - x); + } + + if (x < 0) { + w += x; // clip off the left edge + x = 0; + } + if (y < 0) { + h += y; // clip off some of the height + y = 0; + } + + if (x + w > width) w = width - x; + if (y + h > height) h = height - y; + + PImage output = new PImage(w, h); + output.parent = parent; + + // oops, the last parameter is the scan size of the *target* buffer + //((BufferedImage) image).getRGB(x, y, w, h, output.pixels, 0, w); + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.getDataElements(x, y, w, h, output.pixels); + + return output; + } + + + /** + * Grab a copy of the current pixel buffer. + */ + public PImage get() { + /* + PImage outgoing = new PImage(width, height); + ((BufferedImage) image).getRGB(0, 0, width, height, + outgoing.pixels, 0, width); + return outgoing; + */ + return get(0, 0, width, height); + } + + + public void set(int x, int y, int argb) { + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return; + //((BufferedImage) image).setRGB(x, y, argb); + getset[0] = argb; + WritableRaster raster = ((BufferedImage) image).getRaster(); + raster.setDataElements(x, y, getset); + } + + + protected void setImpl(int dx, int dy, int sx, int sy, int sw, int sh, + PImage src) { + WritableRaster raster = ((BufferedImage) image).getRaster(); + if ((sx == 0) && (sy == 0) && (sw == src.width) && (sh == src.height)) { + raster.setDataElements(dx, dy, src.width, src.height, src.pixels); + } else { + int mode = src.imageMode; + src.imageMode = CORNER; + // TODO Optimize, incredibly inefficient to reallocate this much memory + PImage temp = src.get(sx, sy, sw, sh); + src.imageMode = mode; + raster.setDataElements(dx, dy, temp.width, temp.height, temp.pixels); + } + } + + + ////////////////////////////////////////////////////////////// + + + public void mask(int alpha[]) { + throw new RuntimeException("mask() cannot be used with JAVA2D"); + } + + + public void mask(PImage alpha) { + throw new RuntimeException("mask() cannot be used with JAVA2D"); + } + + + ////////////////////////////////////////////////////////////// + + + public void filter(int kind) { + loadPixels(); + super.filter(kind); + updatePixels(); + } + + + public void filter(int kind, float param) { + loadPixels(); + super.filter(kind, param); + updatePixels(); + } + + + ////////////////////////////////////////////////////////////// + + + public void copy(int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh) { + if ((sw != dw) || (sh != dh)) { + // use slow version if changing size + copy(this, sx, sy, sw, sh, dx, dy, dw, dh); + + } else { + if (imageMode == CORNERS) { + sw -= sx; + sh -= sy; + } + dx = dx - sx; // java2d's "dx" is the delta, not dest + dy = dy - sy; + g2.copyArea(sx, sy, sw, sh, dx, dy); + } + } + + + public void copy(PImage src, + int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2) { + loadPixels(); + super.copy(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); + updatePixels(); + } + + + ////////////////////////////////////////////////////////////// + + + /* + public void blend(PImage src, int sx, int sy, int dx, int dy, int mode) { + loadPixels(); + super.blend(src, sx, sy, dx, dy, mode); + updatePixels(); + } + + + public void blend(int sx, int sy, int dx, int dy, int mode) { + loadPixels(); + super.blend(sx, sy, dx, dy, mode); + updatePixels(); + } + */ + + + public void blend(int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2, int mode) { + loadPixels(); + super.blend(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + updatePixels(); + } + + + public void blend(PImage src, int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2, int mode) { + loadPixels(); + super.blend(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + updatePixels(); + } + + + ////////////////////////////////////////////////////////////// + + + public void save(String filename) { + //System.out.println("start load"); + loadPixels(); + //System.out.println("end load, start save"); + super.save(filename); + //System.out.println("done with save"); + } +} diff --git a/core/PImage.java b/core/PImage.java new file mode 100644 index 000000000..3c42c7107 --- /dev/null +++ b/core/PImage.java @@ -0,0 +1,2598 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.image.*; +import java.io.*; +import java.lang.reflect.*; + + +/** + * Storage class for pixel data. This is the base class for most image and + * pixel information, such as PGraphics and the video library classes. + *

+ * Code for copying, resizing, scaling, and blending contributed + * by toxi + *

+ */ +public class PImage implements PConstants, Cloneable { + + /** + * Format for this image, one of RGB, ARGB or ALPHA. + * note that RGB images still require 0xff in the high byte + * because of how they'll be manipulated by other functions + */ + public int format; + + public int pixels[]; + public int width, height; + // would scan line be useful? maybe for pow of 2 gl textures + + /** + * Path to parent object that will be used with save(). + * This prevents users from needing savePath() to use PImage.save(). + */ + public PApplet parent; + + // note! inherited by PGraphics + public int imageMode = CORNER; + public boolean smooth = false; + + /** native storage for java 1.3 image object */ + //public Object image; + + /** for subclasses that need to store info about the image */ + public Object cache; + + /** modified portion of the image */ + public boolean modified; + public int mx1, my1, mx2, my2; + + // private fields + private int fracU, ifU, fracV, ifV, u1, u2, v1, v2, sX, sY, iw, iw1, ih1; + private int ul, ll, ur, lr, cUL, cLL, cUR, cLR; + private int srcXOffset, srcYOffset; + private int r, g, b, a; + private int[] srcBuffer; + + // fixed point precision is limited to 15 bits!! + static final int PRECISIONB = 15; + static final int PRECISIONF = 1 << PRECISIONB; + static final int PREC_MAXVAL = PRECISIONF-1; + static final int PREC_ALPHA_SHIFT = 24-PRECISIONB; + static final int PREC_RED_SHIFT = 16-PRECISIONB; + + // internal kernel stuff for the gaussian blur filter + int blurRadius; + int blurKernelSize; + int[] blurKernel; + int[][] blurMult; + + + ////////////////////////////////////////////////////////////// + + + /** + * Create an empty image object, set its format to RGB. + * The pixel array is not allocated. + */ + public PImage() { + //format = RGB; // makes sure that this guy is useful + format = ARGB; // default to ARGB images for release 0116 + cache = null; + } + + + /** + * Create a new RGB (alpha ignored) image of a specific size. + * All pixels are set to zero, meaning black, but since the + * alpha is zero, it will be transparent. + */ + public PImage(int width, int height) { + init(width, height, RGB); + //init(width, height, RGB); + //this(new int[width * height], width, height, ARGB); + // toxi: is it maybe better to init the image with max alpha enabled? + //for(int i=0; i + * For subclasses where the pixels[] buffer isn't set by default, + * this should copy all data into the pixels[] array + */ + public void loadPixels() { // ignore + } + + + /** + * Call this when finished messing with the pixels[] array. + *

+ * Mark all pixels as needing update. + */ + public void updatePixels() { + updatePixels(0, 0, width, height); + } + + + /** + * Mark the pixels in this region as needing an update. + *

+ * This is not currently used by any of the renderers, however the api + * is structured this way in the hope of being able to use this to + * speed things up in the future. + *

+ * Note that when using imageMode(CORNERS), + * the x2 and y2 positions are non-inclusive. + */ + public void updatePixels(int x1, int y1, int x2, int y2) { + + if (imageMode == CORNER) { // x2, y2 are w/h + x2 += x1; + y2 += y1; + } + + if (!modified) { + mx1 = x1; + mx2 = x2; + my1 = y1; + my2 = y2; + modified = true; + + } else { + if (x1 < mx1) mx1 = x1; + if (x1 > mx2) mx2 = x1; + if (y1 < my1) my1 = y1; + if (y1 > my2) my2 = y1; + + if (x2 < mx1) mx1 = x2; + if (x2 > mx2) mx2 = x2; + if (y2 < my1) my1 = y2; + if (y2 > my2) my2 = y2; + } + } + + + + ////////////////////////////////////////////////////////////// + + // GET/SET PIXELS + + + /** + * Returns an ARGB "color" type (a packed 32 bit int with the color. + * If the coordinate is outside the image, zero is returned + * (black, but completely transparent). + *

+ * If the image is in RGB format (i.e. on a PVideo object), + * the value will get its high bits set, just to avoid cases where + * they haven't been set already. + *

+ * If the image is in ALPHA format, this returns a white with its + * alpha value set. + *

+ * This function is included primarily for beginners. It is quite + * slow because it has to check to see if the x, y that was provided + * is inside the bounds, and then has to check to see what image + * type it is. If you want things to be more efficient, access the + * pixels[] array directly. + */ + public int get(int x, int y) { + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0; + + switch (format) { + case RGB: + return pixels[y*width + x] | 0xff000000; + + case ARGB: + return pixels[y*width + x]; + + case ALPHA: + return (pixels[y*width + x] << 24) | 0xffffff; + } + return 0; + } + + + /** + * Grab a subsection of a PImage, and copy it into a fresh PImage. + * This honors imageMode() for the coordinates. + */ + public PImage get(int x, int y, int w, int h) { + if (imageMode == CORNERS) { // if CORNER, do nothing + //x2 += x1; y2 += y1; + // w/h are x2/y2 in this case, bring em down to size + w = (w - x); + h = (h - x); + } + + if (x < 0) { + w += x; // clip off the left edge + x = 0; + } + if (y < 0) { + h += y; // clip off some of the height + y = 0; + } + + if (x + w > width) w = width - x; + if (y + h > height) h = height - y; + + PImage newbie = new PImage(w, h, format); + newbie.parent = parent; + + int index = y*width + x; + int index2 = 0; + for (int row = y; row < y+h; row++) { + System.arraycopy(pixels, index, + newbie.pixels, index2, w); + index+=width; + index2+=w; + } + return newbie; + } + + + /** + * Returns a copy of this PImage. Equivalent to get(0, 0, width, height). + */ + public PImage get() { + try { + return (PImage) clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + + /** + * Silently ignores if the coordinate is outside the image. + */ + public void set(int x, int y, int c) { + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return; + pixels[y*width + x] = c; + } + + + public void set(int dx, int dy, PImage src) { + int sx = 0; + int sy = 0; + int sw = src.width; + int sh = src.height; + + if (dx < 0) { // off left edge + sx -= dx; + sw += dx; + dx = 0; + } + if (dy < 0) { // off top edge + sy -= dy; + sh += dy; + dy = 0; + } + if (dx + sw > width) { // off right edge + sw = width - dx; + } + if (dy + sh > height) { // off bottom edge + sh = height - dy; + } + + // this could be nonexistant + if ((sw <= 0) || (sh <= 0)) return; + + setImpl(dx, dy, sx, sy, sw, sh, src); + } + + + /** + * Internal function to actually handle setting a block of pixels that + * has already been properly cropped from the image to a valid region. + */ + protected void setImpl(int dx, int dy, int sx, int sy, int sw, int sh, + PImage src) { + int srcOffset = sy * src.width + sx; + int dstOffset = dy * width + dx; + + for (int y = sy; y < sy + sh; y++) { + System.arraycopy(src.pixels, srcOffset, pixels, dstOffset, sw); + srcOffset += src.width; + dstOffset += width; + } + } + + + + ////////////////////////////////////////////////////////////// + + // ALPHA CHANNEL + + + /** + * Set alpha channel for an image. Black colors in the source + * image will make the destination image completely transparent, + * and white will make things fully opaque. Gray values will + * be in-between steps. + *

+ * Strictly speaking the "blue" value from the source image is + * used as the alpha color. For a fully grayscale image, this + * is correct, but for a color image it's not 100% accurate. + * For a more accurate conversion, first use filter(GRAY) + * which will make the image into a "correct" grayscake by + * performing a proper luminance-based conversion. + */ + public void mask(int alpha[]) { + // don't execute if mask image is different size + if (alpha.length != pixels.length) { + throw new RuntimeException("The PImage used with mask() must be " + + "the same size as the applet."); + } + for (int i = 0; i < pixels.length; i++) { + pixels[i] = ((alpha[i] & 0xff) << 24) | (pixels[i] & 0xffffff); + } + format = ARGB; + } + + + /** + * Set alpha channel for an image using another image as the source. + */ + public void mask(PImage alpha) { + mask(alpha.pixels); + } + + + /** + * Method to apply a variety of basic filters to this image. + *

+ *

+ * Luminance conversion code contributed by + * toxi + *

+ * Gaussian blur code contributed by + * Mario Klingemann + */ + public void filter(int kind) { + loadPixels(); + + switch (kind) { + case BLUR: + // TODO write basic low-pass filter blur here + // what does photoshop do on the edges with this guy? + // better yet.. why bother? just use gaussian with radius 1 + filter(BLUR, 1); + break; + + case GRAY: + if (format == ALPHA) { + // for an alpha image, convert it to an opaque grayscale + for (int i = 0; i < pixels.length; i++) { + int col = 255 - pixels[i]; + pixels[i] = 0xff000000 | (col << 16) | (col << 8) | col; + } + format = RGB; + + } else { + // Converts RGB image data into grayscale using + // weighted RGB components, and keeps alpha channel intact. + // [toxi 040115] + for (int i = 0; i < pixels.length; i++) { + int col = pixels[i]; + // luminance = 0.3*red + 0.59*green + 0.11*blue + // 0.30 * 256 = 77 + // 0.59 * 256 = 151 + // 0.11 * 256 = 28 + int lum = (77*(col>>16&0xff) + 151*(col>>8&0xff) + 28*(col&0xff))>>8; + pixels[i] = (col & ALPHA_MASK) | lum<<16 | lum<<8 | lum; + } + } + break; + + case INVERT: + for (int i = 0; i < pixels.length; i++) { + //pixels[i] = 0xff000000 | + pixels[i] ^= 0xffffff; + } + break; + + case POSTERIZE: + throw new RuntimeException("Use filter(POSTERIZE, int levels) " + + "instead of filter(POSTERIZE)"); + + case RGB: + for (int i = 0; i < pixels.length; i++) { + pixels[i] |= 0xff000000; + } + format = RGB; + break; + + case THRESHOLD: + filter(THRESHOLD, 0.5f); + break; + + // [toxi20050728] added new filters + case ERODE: + dilate(true); + break; + + case DILATE: + dilate(false); + break; + } + updatePixels(); // mark as modified + } + + + /** + * Method to apply a variety of basic filters to this image. + * These filters all take a parameter. + *

+ *

+ * Gaussian blur code contributed by + * Mario Klingemann + * and later updated by toxi for better speed. + */ + public void filter(int kind, float param) { + loadPixels(); + + switch (kind) { + case BLUR: + if (format == ALPHA) + blurAlpha(param); + else if (format == ARGB) + blurARGB(param); + else + blurRGB(param); + break; + + case GRAY: + throw new RuntimeException("Use filter(GRAY) instead of " + + "filter(GRAY, param)"); + + case INVERT: + throw new RuntimeException("Use filter(INVERT) instead of " + + "filter(INVERT, param)"); + + case OPAQUE: + throw new RuntimeException("Use filter(OPAQUE) instead of " + + "filter(OPAQUE, param)"); + + case POSTERIZE: + int levels = (int)param; + if ((levels < 2) || (levels > 255)) { + throw new RuntimeException("Levels must be between 2 and 255 for " + + "filter(POSTERIZE, levels)"); + } + int levels1 = levels - 1; + for (int i = 0; i < pixels.length; i++) { + int rlevel = (pixels[i] >> 16) & 0xff; + int glevel = (pixels[i] >> 8) & 0xff; + int blevel = pixels[i] & 0xff; + rlevel = (((rlevel * levels) >> 8) * 255) / levels1; + glevel = (((glevel * levels) >> 8) * 255) / levels1; + blevel = (((blevel * levels) >> 8) * 255) / levels1; + pixels[i] = ((0xff000000 & pixels[i]) | + (rlevel << 16) | + (glevel << 8) | + blevel); + } + break; + + case THRESHOLD: // greater than or equal to the threshold + int thresh = (int) (param * 255); + for (int i = 0; i < pixels.length; i++) { + int max = Math.max((pixels[i] & RED_MASK) >> 16, + Math.max((pixels[i] & GREEN_MASK) >> 8, + (pixels[i] & BLUE_MASK))); + pixels[i] = (pixels[i] & ALPHA_MASK) | + ((max < thresh) ? 0x000000 : 0xffffff); + } + break; + + // [toxi20050728] added new filters + case ERODE: + throw new RuntimeException("Use filter(ERODE) instead of " + + "filter(ERODE, param)"); + case DILATE: + throw new RuntimeException("Use filter(DILATE) instead of " + + "filter(DILATE, param)"); + } + updatePixels(); // mark as modified + } + + + /** + * Optimized code for building the blur kernel. + * further optimized blur code (approx. 15% for radius=20) + * bigger speed gains for larger radii (~30%) + * added support for various image types (ALPHA, RGB, ARGB) + * [toxi 050728] + */ + protected void buildBlurKernel(float r) { + int radius = (int) (r * 3.5f); + radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248); + if (blurRadius != radius) { + blurRadius = radius; + blurKernelSize = 1 + blurRadius<<1; + blurKernel = new int[blurKernelSize]; + blurMult = new int[blurKernelSize][256]; + + int bk,bki; + int[] bm,bmi; + + for (int i = 1, radiusi = radius - 1; i < radius; i++) { + blurKernel[radius+i] = blurKernel[radiusi] = bki = radiusi * radiusi; + bm=blurMult[radius+i]; + bmi=blurMult[radiusi--]; + for (int j = 0; j < 256; j++) + bm[j] = bmi[j] = bki*j; + } + bk = blurKernel[radius] = radius * radius; + bm = blurMult[radius]; + for (int j = 0; j < 256; j++) + bm[j] = bk*j; + } + } + + protected void blurAlpha(float r) { + int sum, /*cr, cg,*/ cb; //, k; + int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0; + int b2[] = new int[pixels.length]; + int yi = 0; + + buildBlurKernel(r); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + //cb = cg = cr = sum = 0; + cb = sum = 0; + read = x - blurRadius; + if (read<0) { + bk0=-read; + read=0; + } else { + if (read >= width) + break; + bk0=0; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (read >= width) + break; + int c = pixels[read + yi]; + int[] bm=blurMult[i]; + cb += bm[c & BLUE_MASK]; + sum += blurKernel[i]; + read++; + } + ri = yi + x; + b2[ri] = cb / sum; + } + yi += width; + } + + yi = 0; + ym=-blurRadius; + ymi=ym*width; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + //cb = cg = cr = sum = 0; + cb = sum = 0; + if (ym<0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= height) + break; + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (ri >= height) + break; + int[] bm=blurMult[i]; + cb += bm[b2[read]]; + sum += blurKernel[i]; + ri++; + read += width; + } + pixels[x+yi] = (cb/sum); + } + yi += width; + ymi += width; + ym++; + } + } + + protected void blurRGB(float r) { + int sum, cr, cg, cb; //, k; + int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0; + int r2[] = new int[pixels.length]; + int g2[] = new int[pixels.length]; + int b2[] = new int[pixels.length]; + int yi = 0; + + buildBlurKernel(r); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cb = cg = cr = sum = 0; + read = x - blurRadius; + if (read<0) { + bk0=-read; + read=0; + } else { + if (read >= width) + break; + bk0=0; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (read >= width) + break; + int c = pixels[read + yi]; + int[] bm=blurMult[i]; + cr += bm[(c & RED_MASK) >> 16]; + cg += bm[(c & GREEN_MASK) >> 8]; + cb += bm[c & BLUE_MASK]; + sum += blurKernel[i]; + read++; + } + ri = yi + x; + r2[ri] = cr / sum; + g2[ri] = cg / sum; + b2[ri] = cb / sum; + } + yi += width; + } + + yi = 0; + ym=-blurRadius; + ymi=ym*width; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cb = cg = cr = sum = 0; + if (ym<0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= height) + break; + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (ri >= height) + break; + int[] bm=blurMult[i]; + cr += bm[r2[read]]; + cg += bm[g2[read]]; + cb += bm[b2[read]]; + sum += blurKernel[i]; + ri++; + read += width; + } + pixels[x+yi] = 0xff000000 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum); + } + yi += width; + ymi += width; + ym++; + } + } + + protected void blurARGB(float r) { + int sum, cr, cg, cb, ca; + int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0; + int wh = pixels.length; + int r2[] = new int[wh]; + int g2[] = new int[wh]; + int b2[] = new int[wh]; + int a2[] = new int[wh]; + int yi = 0; + + buildBlurKernel(r); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cb = cg = cr = ca = sum = 0; + read = x - blurRadius; + if (read<0) { + bk0=-read; + read=0; + } else { + if (read >= width) + break; + bk0=0; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (read >= width) + break; + int c = pixels[read + yi]; + int[] bm=blurMult[i]; + ca += bm[(c & ALPHA_MASK) >>> 24]; + cr += bm[(c & RED_MASK) >> 16]; + cg += bm[(c & GREEN_MASK) >> 8]; + cb += bm[c & BLUE_MASK]; + sum += blurKernel[i]; + read++; + } + ri = yi + x; + a2[ri] = ca / sum; + r2[ri] = cr / sum; + g2[ri] = cg / sum; + b2[ri] = cb / sum; + } + yi += width; + } + + yi = 0; + ym=-blurRadius; + ymi=ym*width; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cb = cg = cr = ca = sum = 0; + if (ym<0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= height) + break; + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (ri >= height) + break; + int[] bm=blurMult[i]; + ca += bm[a2[read]]; + cr += bm[r2[read]]; + cg += bm[g2[read]]; + cb += bm[b2[read]]; + sum += blurKernel[i]; + ri++; + read += width; + } + pixels[x+yi] = (ca/sum)<<24 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum); + } + yi += width; + ymi += width; + ym++; + } + } + + /** + * Generic dilate/erode filter using luminance values + * as decision factor. [toxi 050728] + */ + protected void dilate(boolean isInverted) { + int currIdx=0; + int maxIdx=pixels.length; + int[] out=new int[maxIdx]; + + if (!isInverted) { + // erosion (grow light areas) + while (currIdx=maxRowIdx) + idxRight=currIdx; + if (idxUp<0) + idxUp=0; + if (idxDown>=maxIdx) + idxDown=currIdx; + + int colUp=pixels[idxUp]; + int colLeft=pixels[idxLeft]; + int colDown=pixels[idxDown]; + int colRight=pixels[idxRight]; + + // compute luminance + int currLum = + 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff); + int lumLeft = + 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff); + int lumRight = + 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff); + int lumUp = + 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff); + int lumDown = + 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff); + + if (lumLeft>currLum) { + colOut=colLeft; + currLum=lumLeft; + } + if (lumRight>currLum) { + colOut=colRight; + currLum=lumRight; + } + if (lumUp>currLum) { + colOut=colUp; + currLum=lumUp; + } + if (lumDown>currLum) { + colOut=colDown; + currLum=lumDown; + } + out[currIdx++]=colOut; + } + } + } else { + // dilate (grow dark areas) + while (currIdx=maxRowIdx) + idxRight=currIdx; + if (idxUp<0) + idxUp=0; + if (idxDown>=maxIdx) + idxDown=currIdx; + + int colUp=pixels[idxUp]; + int colLeft=pixels[idxLeft]; + int colDown=pixels[idxDown]; + int colRight=pixels[idxRight]; + + // compute luminance + int currLum = + 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff); + int lumLeft = + 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff); + int lumRight = + 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff); + int lumUp = + 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff); + int lumDown = + 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff); + + if (lumLeft + *
  • REPLACE - destination colour equals colour of source pixel: C = A. + * Sometimes called "Normal" or "Copy" in other software. + * + *
  • BLEND - linear interpolation of colours: + * C = A*factor + B + * + *
  • ADD - additive blending with white clip: + * C = min(A*factor + B, 255). + * Clipped to 0..255, Photoshop calls this "Linear Burn", + * and Director calls it "Add Pin". + * + *
  • SUBTRACT - substractive blend with black clip: + * C = max(B - A*factor, 0). + * Clipped to 0..255, Photoshop calls this "Linear Dodge", + * and Director calls it "Subtract Pin". + * + *
  • DARKEST - only the darkest colour succeeds: + * C = min(A*factor, B). + * Illustrator calls this "Darken". + * + *
  • LIGHTEST - only the lightest colour succeeds: + * C = max(A*factor, B). + * Illustrator calls this "Lighten". + * + *
  • DIFFERENCE - subtract colors from underlying image. + * + *
  • EXCLUSION - similar to DIFFERENCE, but less extreme. + * + *
  • MULTIPLY - Multiply the colors, result will always be darker. + * + *
  • SCREEN - Opposite multiply, uses inverse values of the colors. + * + *
  • OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, + * and screens light values. + * + *
  • HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower. + * + *
  • SOFT_LIGHT - Mix of DARKEST and LIGHTEST. + * Works like OVERLAY, but not as harsh. + * + *
  • DODGE - Lightens light tones and increases contrast, ignores darks. + * Called "Color Dodge" in Illustrator and Photoshop. + * + *
  • BURN - Darker areas are applied, increasing contrast, ignores lights. + * Called "Color Burn" in Illustrator and Photoshop. + * + *

    A useful reference for blending modes and their algorithms can be + * found in the SVG + * specification.

    + *

    It is important to note that Processing uses "fast" code, not + * necessarily "correct" code. No biggie, most software does. A nitpicker + * can find numerous "off by 1 division" problems in the blend code where + * >>8 or >>7 is used when strictly speaking + * /255.0 or /127.0 should have been used.

    + *

    For instance, exclusion (not intended for real-time use) reads + * r1 + r2 - ((2 * r1 * r2) / 255) because 255 == 1.0 + * not 256 == 1.0. In other words, (255*255)>>8 is not + * the same as (255*255)/255. But for real-time use the shifts + * are preferrable, and the difference is insignificant for applications + * built with Processing.

    + */ + static public int blendColor(int c1, int c2, int mode) { + switch (mode) { + case REPLACE: return c2; + case BLEND: return blend_blend(c1, c2); + + case ADD: return blend_add_pin(c1, c2); + case SUBTRACT: return blend_sub_pin(c1, c2); + + case LIGHTEST: return blend_lightest(c1, c2); + case DARKEST: return blend_darkest(c1, c2); + + case DIFFERENCE: return blend_difference(c1, c2); + case EXCLUSION: return blend_exclusion(c1, c2); + + case MULTIPLY: return blend_multiply(c1, c2); + case SCREEN: return blend_screen(c1, c2); + + case HARD_LIGHT: return blend_hard_light(c1, c2); + case SOFT_LIGHT: return blend_soft_light(c1, c2); + case OVERLAY: return blend_overlay(c1, c2); + + case DODGE: return blend_dodge(c1, c2); + case BURN: return blend_burn(c1, c2); + } + return 0; + } + + + /** + * Blends one area of this image to another area. + * @see processing.core.PImage#blendColor(int,int,int) + */ + public void blend(int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2, int mode) { + blend(this, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + } + + + /** + * Copies area of one image into another PImage object. + * @see processing.core.PImage#blendColor(int,int,int) + */ + public void blend(PImage src, + int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2, int mode) { + if (imageMode == CORNER) { // if CORNERS, do nothing + sx2 += sx1; sy2 += sy1; + dx2 += dx1; dy2 += dy1; + + //} else if (imageMode == CENTER) { + //sx2 /= 2f; sy2 /= 2f; + //dx2 /= 2f; dy2 /= 2f; + } + + if ((src == this) && + intersect(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2)) { + blit_resize(get(sx1, sy1, sx2 - sx1, sy2 - sy1), + 0, 0, sx2 - sx1 - 1, sy2 - sy1 - 1, + pixels, width, height, dx1, dy1, dx2, dy2, mode); + } else { + blit_resize(src, sx1, sy1, sx2, sy2, + pixels, width, height, dx1, dy1, dx2, dy2, mode); + } + } + + + /** + * Check to see if two rectangles intersect one another + */ + protected boolean intersect(int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2) { + int sw = sx2 - sx1 + 1; + int sh = sy2 - sy1 + 1; + int dw = dx2 - dx1 + 1; + int dh = dy2 - dy1 + 1; + + if (dx1 < sx1) { + dw += dx1 - sx1; + if (dw > sw) { + dw = sw; + } + } else { + int w = sw + sx1 - dx1; + if (dw > w) { + dw = w; + } + } + if (dy1 < sy1) { + dh += dy1 - sy1; + if (dh > sh) { + dh = sh; + } + } else { + int h = sh + sy1 - dy1; + if (dh > h) { + dh = h; + } + } + return !(dw <= 0 || dh <= 0); + } + + + + ////////////////////////////////////////////////////////////// + + // COPYING IMAGE DATA + + + /** + * Duplicate an image, returns new PImage object. + * The pixels[] array for the new object will be unique + * and recopied from the source image. This is implemented as an + * override of Object.clone(). We recommend using get() instead, + * because it prevents you from needing to catch the + * CloneNotSupportedException, and from doing a cast from the result. + */ + public Object clone() throws CloneNotSupportedException { // ignore + PImage c = (PImage) super.clone(); + + // super.clone() will only copy the reference to the pixels + // array, so this will do a proper duplication of it instead. + c.pixels = new int[width * height]; + System.arraycopy(pixels, 0, c.pixels, 0, pixels.length); + + // return the goods + return c; + } + + + + ////////////////////////////////////////////////////////////// + + /** + * Internal blitter/resizer/copier from toxi. + * Uses bilinear filtering if smooth() has been enabled + * 'mode' determines the blending mode used in the process. + */ + private void blit_resize(PImage img, + int srcX1, int srcY1, int srcX2, int srcY2, + int[] destPixels, int screenW, int screenH, + int destX1, int destY1, int destX2, int destY2, + int mode) { + if (srcX1 < 0) srcX1 = 0; + if (srcY1 < 0) srcY1 = 0; + if (srcX2 >= img.width) srcX2 = img.width - 1; + if (srcY2 >= img.height) srcY2 = img.height - 1; + + int srcW = srcX2 - srcX1; + int srcH = srcY2 - srcY1; + int destW = destX2 - destX1; + int destH = destY2 - destY1; + + if (!smooth) { + srcW++; srcH++; + } + + if (destW <= 0 || destH <= 0 || + srcW <= 0 || srcH <= 0 || + destX1 >= screenW || destY1 >= screenH || + srcX1 >= img.width || srcY1 >= img.height) { + return; + } + + int dx = (int) (srcW / (float) destW * PRECISIONF); + int dy = (int) (srcH / (float) destH * PRECISIONF); + + srcXOffset = (int) (destX1 < 0 ? -destX1 * dx : srcX1 * PRECISIONF); + srcYOffset = (int) (destY1 < 0 ? -destY1 * dy : srcY1 * PRECISIONF); + + if (destX1 < 0) { + destW += destX1; + destX1 = 0; + } + if (destY1 < 0) { + destH += destY1; + destY1 = 0; + } + + destW = low(destW, screenW - destX1); + destH = low(destH, screenH - destY1); + + int destOffset = destY1 * screenW + destX1; + srcBuffer = img.pixels; + + if (smooth) { + // use bilinear filtering + iw = img.width; + iw1 = img.width - 1; + ih1 = img.height - 1; + + switch (mode) { + + case BLEND: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + // davbol - renamed old blend_multiply to blend_blend + destPixels[destOffset + x] = + blend_blend(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case ADD: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_add_pin(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SUBTRACT: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_sub_pin(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case LIGHTEST: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_lightest(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case DARKEST: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_darkest(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case REPLACE: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = filter_bilinear(); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case DIFFERENCE: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_difference(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case EXCLUSION: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_exclusion(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case MULTIPLY: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_multiply(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SCREEN: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_screen(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case OVERLAY: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_overlay(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case HARD_LIGHT: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_hard_light(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SOFT_LIGHT: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_soft_light(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + // davbol - proposed 2007-01-09 + case DODGE: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_dodge(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case BURN: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_burn(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + } + + } else { + // nearest neighbour scaling (++fast!) + switch (mode) { + + case BLEND: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + // davbol - renamed old blend_multiply to blend_blend + destPixels[destOffset + x] = + blend_blend(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case ADD: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_add_pin(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SUBTRACT: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_sub_pin(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case LIGHTEST: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_lightest(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case DARKEST: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_darkest(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case REPLACE: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = srcBuffer[sY + (sX >> PRECISIONB)]; + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case DIFFERENCE: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_difference(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case EXCLUSION: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_exclusion(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case MULTIPLY: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_multiply(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SCREEN: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_screen(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case OVERLAY: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_overlay(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case HARD_LIGHT: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_hard_light(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SOFT_LIGHT: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_soft_light(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + // davbol - proposed 2007-01-09 + case DODGE: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_dodge(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case BURN: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_burn(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + } + } + } + + + private void filter_new_scanline() { + sX = srcXOffset; + fracV = srcYOffset & PREC_MAXVAL; + ifV = PREC_MAXVAL - fracV; + v1 = (srcYOffset >> PRECISIONB) * iw; + v2 = low((srcYOffset >> PRECISIONB) + 1, ih1) * iw; + } + + + private int filter_bilinear() { + fracU = sX & PREC_MAXVAL; + ifU = PREC_MAXVAL - fracU; + ul = (ifU * ifV) >> PRECISIONB; + ll = (ifU * fracV) >> PRECISIONB; + ur = (fracU * ifV) >> PRECISIONB; + lr = (fracU * fracV) >> PRECISIONB; + u1 = (sX >> PRECISIONB); + u2 = low(u1 + 1, iw1); + + // get color values of the 4 neighbouring texels + cUL = srcBuffer[v1 + u1]; + cUR = srcBuffer[v1 + u2]; + cLL = srcBuffer[v2 + u1]; + cLR = srcBuffer[v2 + u2]; + + r = ((ul*((cUL&RED_MASK)>>16) + ll*((cLL&RED_MASK)>>16) + + ur*((cUR&RED_MASK)>>16) + lr*((cLR&RED_MASK)>>16)) + << PREC_RED_SHIFT) & RED_MASK; + + g = ((ul*(cUL&GREEN_MASK) + ll*(cLL&GREEN_MASK) + + ur*(cUR&GREEN_MASK) + lr*(cLR&GREEN_MASK)) + >>> PRECISIONB) & GREEN_MASK; + + b = (ul*(cUL&BLUE_MASK) + ll*(cLL&BLUE_MASK) + + ur*(cUR&BLUE_MASK) + lr*(cLR&BLUE_MASK)) + >>> PRECISIONB; + + a = ((ul*((cUL&ALPHA_MASK)>>>24) + ll*((cLL&ALPHA_MASK)>>>24) + + ur*((cUR&ALPHA_MASK)>>>24) + lr*((cLR&ALPHA_MASK)>>>24)) + << PREC_ALPHA_SHIFT) & ALPHA_MASK; + + return a | r | g | b; + } + + + + ////////////////////////////////////////////////////////////// + + // internal blending methods + + + private static int low(int a, int b) { + return (a < b) ? a : b; + } + + + private static int high(int a, int b) { + return (a > b) ? a : b; + } + + // davbol - added peg helper, equiv to constrain(n,0,255) + private static int peg(int n) { + return (n < 0) ? 0 : ((n > 255) ? 255 : n); + } + + private static int mix(int a, int b, int f) { + return a + (((b - a) * f) >> 8); + } + + + + ///////////////////////////////////////////////////////////// + + // BLEND MODE IMPLEMENTIONS + + + private static int blend_blend(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + mix(a & RED_MASK, b & RED_MASK, f) & RED_MASK | + mix(a & GREEN_MASK, b & GREEN_MASK, f) & GREEN_MASK | + mix(a & BLUE_MASK, b & BLUE_MASK, f)); + } + + + /** + * additive blend with clipping + */ + private static int blend_add_pin(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + low(((a & RED_MASK) + + ((b & RED_MASK) >> 8) * f), RED_MASK) & RED_MASK | + low(((a & GREEN_MASK) + + ((b & GREEN_MASK) >> 8) * f), GREEN_MASK) & GREEN_MASK | + low((a & BLUE_MASK) + + (((b & BLUE_MASK) * f) >> 8), BLUE_MASK)); + } + + + /** + * subtractive blend with clipping + */ + private static int blend_sub_pin(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + high(((a & RED_MASK) - ((b & RED_MASK) >> 8) * f), + GREEN_MASK) & RED_MASK | + high(((a & GREEN_MASK) - ((b & GREEN_MASK) >> 8) * f), + BLUE_MASK) & GREEN_MASK | + high((a & BLUE_MASK) - (((b & BLUE_MASK) * f) >> 8), 0)); + } + + + /** + * only returns the blended lightest colour + */ + private static int blend_lightest(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + high(a & RED_MASK, ((b & RED_MASK) >> 8) * f) & RED_MASK | + high(a & GREEN_MASK, ((b & GREEN_MASK) >> 8) * f) & GREEN_MASK | + high(a & BLUE_MASK, ((b & BLUE_MASK) * f) >> 8)); + } + + + /** + * only returns the blended darkest colour + */ + private static int blend_darkest(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + mix(a & RED_MASK, + low(a & RED_MASK, + ((b & RED_MASK) >> 8) * f), f) & RED_MASK | + mix(a & GREEN_MASK, + low(a & GREEN_MASK, + ((b & GREEN_MASK) >> 8) * f), f) & GREEN_MASK | + mix(a & BLUE_MASK, + low(a & BLUE_MASK, + ((b & BLUE_MASK) * f) >> 8), f)); + } + + + /** + * returns the absolute value of the difference of the input colors + * C = |A - B| + */ + private static int blend_difference(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (ar > br) ? (ar-br) : (br-ar); + int cg = (ag > bg) ? (ag-bg) : (bg-ag); + int cb = (ab > bb) ? (ab-bb) : (bb-ab); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * Cousin of difference, algorithm used here is based on a Lingo version + * found here: http://www.mediamacros.com/item/item-1006687616/ + * (Not yet verified to be correct). + */ + private static int blend_exclusion(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = ar + br - ((ar * br) >> 7); + int cg = ag + bg - ((ag * bg) >> 7); + int cb = ab + bb - ((ab * bb) >> 7); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns the product of the input colors + * C = A * B + */ + private static int blend_multiply(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (ar * br) >> 8; + int cg = (ag * bg) >> 8; + int cb = (ab * bb) >> 8; + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns the inverse of the product of the inverses of the input colors + * (the inverse of multiply). C = 1 - (1-A) * (1-B) + */ + private static int blend_screen(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = 255 - (((255 - ar) * (255 - br)) >> 8); + int cg = 255 - (((255 - ag) * (255 - bg)) >> 8); + int cb = 255 - (((255 - ab) * (255 - bb)) >> 8); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns either multiply or screen for darker or lighter values of A + * (the inverse of hard light) + * C = + * A < 0.5 : 2 * A * B + * A >=0.5 : 1 - (2 * (255-A) * (255-B)) + */ + private static int blend_overlay(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (ar < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7)); + int cg = (ag < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7)); + int cb = (ab < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7)); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns either multiply or screen for darker or lighter values of B + * (the inverse of overlay) + * C = + * B < 0.5 : 2 * A * B + * B >=0.5 : 1 - (2 * (255-A) * (255-B)) + */ + private static int blend_hard_light(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (br < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7)); + int cg = (bg < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7)); + int cb = (bb < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7)); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns the inverse multiply plus screen, which simplifies to + * C = 2AB + A^2 - 2A^2B + */ + private static int blend_soft_light(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = ((ar*br)>>7) + ((ar*ar)>>8) - ((ar*ar*br)>>15); + int cg = ((ag*bg)>>7) + ((ag*ag)>>8) - ((ag*ag*bg)>>15); + int cb = ((ab*bb)>>7) + ((ab*ab)>>8) - ((ab*ab*bb)>>15); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * Returns the first (underlay) color divided by the inverse of + * the second (overlay) color. C = A / (255-B) + */ + private static int blend_dodge(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (br==255) ? 255 : peg((ar << 8) / (255 - br)); // division requires pre-peg()-ing + int cg = (bg==255) ? 255 : peg((ag << 8) / (255 - bg)); // " + int cb = (bb==255) ? 255 : peg((ab << 8) / (255 - bb)); // " + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns the inverse of the inverse of the first (underlay) color + * divided by the second (overlay) color. C = 255 - (255-A) / B + */ + private static int blend_burn(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (br==0) ? 0 : 255 - peg(((255 - ar) << 8) / br); // division requires pre-peg()-ing + int cg = (bg==0) ? 0 : 255 - peg(((255 - ag) << 8) / bg); // " + int cb = (bb==0) ? 0 : 255 - peg(((255 - ab) << 8) / bb); // " + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + ////////////////////////////////////////////////////////////// + + // FILE I/O + + + static byte TIFF_HEADER[] = { + 77, 77, 0, 42, 0, 0, 0, 8, 0, 9, 0, -2, 0, 4, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 2, 0, 3, 0, 0, 0, 3, 0, 0, 0, 122, 1, 6, 0, 3, 0, + 0, 0, 1, 0, 2, 0, 0, 1, 17, 0, 4, 0, 0, 0, 1, 0, 0, 3, 0, 1, 21, + 0, 3, 0, 0, 0, 1, 0, 3, 0, 0, 1, 22, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 23, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 8, 0, 8 + }; + + + /* + protected boolean saveHeaderTIFF(OutputStream output) { + try { + byte tiff[] = new byte[768]; + System.arraycopy(tiff_header, 0, tiff, 0, tiff_header.length); + + tiff[30] = (byte) ((width >> 8) & 0xff); + tiff[31] = (byte) ((width) & 0xff); + tiff[42] = tiff[102] = (byte) ((height >> 8) & 0xff); + tiff[43] = tiff[103] = (byte) ((height) & 0xff); + + int count = width*height*3; + tiff[114] = (byte) ((count >> 24) & 0xff); + tiff[115] = (byte) ((count >> 16) & 0xff); + tiff[116] = (byte) ((count >> 8) & 0xff); + tiff[117] = (byte) ((count) & 0xff); + + output.write(tiff); + return true; + + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + */ + + + static final String TIFF_ERROR = + "Error: Processing can only read its own TIFF files."; + + static protected PImage loadTIFF(byte tiff[]) { + if ((tiff[42] != tiff[102]) || // width/height in both places + (tiff[43] != tiff[103])) { + System.err.println(TIFF_ERROR); + return null; + } + + int width = + ((tiff[30] & 0xff) << 8) | (tiff[31] & 0xff); + int height = + ((tiff[42] & 0xff) << 8) | (tiff[43] & 0xff); + + int count = + ((tiff[114] & 0xff) << 24) | + ((tiff[115] & 0xff) << 16) | + ((tiff[116] & 0xff) << 8) | + (tiff[117] & 0xff); + if (count != width * height * 3) { + System.err.println(TIFF_ERROR + " (" + width + ", " + height +")"); + return null; + } + + // check the rest of the header + for (int i = 0; i < TIFF_HEADER.length; i++) { + if ((i == 30) || (i == 31) || (i == 42) || (i == 43) || + (i == 102) || (i == 103) || + (i == 114) || (i == 115) || (i == 116) || (i == 117)) continue; + + if (tiff[i] != TIFF_HEADER[i]) { + System.err.println(TIFF_ERROR + " (" + i + ")"); + return null; + } + } + + PImage outgoing = new PImage(width, height, RGB); + int index = 768; + count /= 3; + for (int i = 0; i < count; i++) { + outgoing.pixels[i] = + 0xFF000000 | + (tiff[index++] & 0xff) << 16 | + (tiff[index++] & 0xff) << 8 | + (tiff[index++] & 0xff); + } + return outgoing; + } + + + protected boolean saveTIFF(OutputStream output) { + // shutting off the warning, people can figure this out themselves + /* + if (format != RGB) { + System.err.println("Warning: only RGB information is saved with " + + ".tif files. Use .tga or .png for ARGB images and others."); + } + */ + try { + byte tiff[] = new byte[768]; + System.arraycopy(TIFF_HEADER, 0, tiff, 0, TIFF_HEADER.length); + + tiff[30] = (byte) ((width >> 8) & 0xff); + tiff[31] = (byte) ((width) & 0xff); + tiff[42] = tiff[102] = (byte) ((height >> 8) & 0xff); + tiff[43] = tiff[103] = (byte) ((height) & 0xff); + + int count = width*height*3; + tiff[114] = (byte) ((count >> 24) & 0xff); + tiff[115] = (byte) ((count >> 16) & 0xff); + tiff[116] = (byte) ((count >> 8) & 0xff); + tiff[117] = (byte) ((count) & 0xff); + + // spew the header to the disk + output.write(tiff); + + for (int i = 0; i < pixels.length; i++) { + output.write((pixels[i] >> 16) & 0xff); + output.write((pixels[i] >> 8) & 0xff); + output.write(pixels[i] & 0xff); + } + output.flush(); + return true; + + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + + /** + * Creates a Targa32 formatted byte sequence of specified + * pixel buffer using RLE compression. + *

    + * Also figured out how to avoid parsing the image upside-down + * (there's a header flag to set the image origin to top-left) + *

    + * Starting with revision 0092, the format setting is taken into account: + *
      + *
    • ALPHA images written as 8bit grayscale (uses lowest byte) + *
    • RGB → 24 bits + *
    • ARGB → 32 bits + *
    + * All versions are RLE compressed. + *

    + * Contributed by toxi 8-10 May 2005, based on this RLE + * specification + */ + protected boolean saveTGA(OutputStream output) { + byte header[] = new byte[18]; + + if (format == ALPHA) { // save ALPHA images as 8bit grayscale + header[2] = 0x0B; + header[16] = 0x08; + header[17] = 0x28; + + } else if (format == RGB) { + header[2] = 0x0A; + header[16] = 24; + header[17] = 0x20; + + } else if (format == ARGB) { + header[2] = 0x0A; + header[16] = 32; + header[17] = 0x28; + + } else { + throw new RuntimeException("Image format not recognized inside save()"); + } + // set image dimensions lo-hi byte order + header[12] = (byte) (width & 0xff); + header[13] = (byte) (width >> 8); + header[14] = (byte) (height & 0xff); + header[15] = (byte) (height >> 8); + + try { + output.write(header); + + int maxLen = height * width; + int index = 0; + int col; //, prevCol; + int[] currChunk = new int[128]; + + // 8bit image exporter is in separate loop + // to avoid excessive conditionals... + if (format == ALPHA) { + while (index < maxLen) { + boolean isRLE = false; + int rle = 1; + currChunk[0] = col = pixels[index] & 0xff; + while (index + rle < maxLen) { + if (col != (pixels[index + rle]&0xff) || rle == 128) { + isRLE = (rle > 1); + break; + } + rle++; + } + if (isRLE) { + output.write(0x80 | (rle - 1)); + output.write(col); + + } else { + rle = 1; + while (index + rle < maxLen) { + int cscan = pixels[index + rle] & 0xff; + if ((col != cscan && rle < 128) || rle < 3) { + currChunk[rle] = col = cscan; + } else { + if (col == cscan) rle -= 2; + break; + } + rle++; + } + output.write(rle - 1); + for (int i = 0; i < rle; i++) output.write(currChunk[i]); + } + index += rle; + } + } else { // export 24/32 bit TARGA + while (index < maxLen) { + boolean isRLE = false; + currChunk[0] = col = pixels[index]; + int rle = 1; + // try to find repeating bytes (min. len = 2 pixels) + // maximum chunk size is 128 pixels + while (index + rle < maxLen) { + if (col != pixels[index + rle] || rle == 128) { + isRLE = (rle > 1); // set flag for RLE chunk + break; + } + rle++; + } + if (isRLE) { + output.write(128 | (rle - 1)); + output.write(col & 0xff); + output.write(col >> 8 & 0xff); + output.write(col >> 16 & 0xff); + if (format == ARGB) output.write(col >>> 24 & 0xff); + + } else { // not RLE + rle = 1; + while (index + rle < maxLen) { + if ((col != pixels[index + rle] && rle < 128) || rle < 3) { + currChunk[rle] = col = pixels[index + rle]; + } else { + // check if the exit condition was the start of + // a repeating colour + if (col == pixels[index + rle]) rle -= 2; + break; + } + rle++; + } + // write uncompressed chunk + output.write(rle - 1); + if (format == ARGB) { + for (int i = 0; i < rle; i++) { + col = currChunk[i]; + output.write(col & 0xff); + output.write(col >> 8 & 0xff); + output.write(col >> 16 & 0xff); + output.write(col >>> 24 & 0xff); + } + } else { + for (int i = 0; i < rle; i++) { + col = currChunk[i]; + output.write(col & 0xff); + output.write(col >> 8 & 0xff); + output.write(col >> 16 & 0xff); + } + } + } + index += rle; + } + } + output.flush(); + return true; + + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + + /** + * Use ImageIO functions from Java 1.4 and later to handle image save. + * Various formats are supported, typically jpeg, png, bmp, and wbmp. + * To get a list of the supported formats for writing, use:
    + * println(javax.imageio.ImageIO.getReaderFormatNames()) + */ + protected void saveImageIO(String path) throws IOException { + try { + //BufferedImage bimage = + // new BufferedImage(width, height, (format == ARGB) ? + // BufferedImage.TYPE_INT_ARGB : + // BufferedImage.TYPE_INT_RGB); + Class bufferedImageClass = + Class.forName("java.awt.image.BufferedImage"); + Constructor bufferedImageConstructor = + bufferedImageClass.getConstructor(new Class[] { + Integer.TYPE, + Integer.TYPE, + Integer.TYPE }); + Field typeIntRgbField = bufferedImageClass.getField("TYPE_INT_RGB"); + int typeIntRgb = typeIntRgbField.getInt(typeIntRgbField); + Field typeIntArgbField = bufferedImageClass.getField("TYPE_INT_ARGB"); + int typeIntArgb = typeIntArgbField.getInt(typeIntArgbField); + Object bimage = + bufferedImageConstructor.newInstance(new Object[] { + new Integer(width), + new Integer(height), + new Integer((format == ARGB) ? typeIntArgb : typeIntRgb) + }); + + //bimage.setRGB(0, 0, width, height, pixels, 0, width); + Method setRgbMethod = + bufferedImageClass.getMethod("setRGB", new Class[] { + Integer.TYPE, Integer.TYPE, + Integer.TYPE, Integer.TYPE, + pixels.getClass(), + Integer.TYPE, Integer.TYPE + }); + setRgbMethod.invoke(bimage, new Object[] { + new Integer(0), new Integer(0), + new Integer(width), new Integer(height), + pixels, new Integer(0), new Integer(width) + }); + + File file = new File(path); + String extension = path.substring(path.lastIndexOf('.') + 1); + + //ImageIO.write(bimage, extension, file); + Class renderedImageClass = + Class.forName("java.awt.image.RenderedImage"); + Class ioClass = Class.forName("javax.imageio.ImageIO"); + Method writeMethod = + ioClass.getMethod("write", new Class[] { + renderedImageClass, String.class, File.class + }); + writeMethod.invoke(null, new Object[] { bimage, extension, file }); + + } catch (Exception e) { + e.printStackTrace(); + throw new IOException("image save failed."); + } + } + + + protected String[] saveImageFormats; + + /** + * Save this image to disk. + *

    + * As of revision 0100, this function requires an absolute path, + * in order to avoid confusion. To save inside the sketch folder, + * use the function savePath() from PApplet, or use saveFrame() instead. + *

    + * As of revision 0115, when using Java 1.4 and later, you can write + * to several formats besides tga and tiff. If Java 1.4 is installed + * and the extension used is supported (usually png, jpg, jpeg, bmp, + * and tiff), then those methods will be used to write the image. + * To get a list of the supported formats for writing, use:
    + * println(javax.imageio.ImageIO.getReaderFormatNames()) + *

    + * To use the original built-in image writers, use .tga as the extension, + * or don't include an extension, in which case .tif will be added. + *

    + * The ImageIO API claims to support wbmp files, however they probably + * require a black and white image. Basic testing produced a zero-length + * file with no error. + *

    + * As of revision 0116, savePath() is not needed if this object has been + * created (as recommended) via createImage() or createGraphics() or + * one of its neighbors. + */ + public void save(String path) { // ignore + boolean success = false; + + File file = new File(path); + if (!file.isAbsolute()) { + if (parent != null) { + //file = new File(parent.savePath(filename)); + path = parent.savePath(path); + } else { + String re = "PImage.save() requires an absolute path. " + + "Use createImage(), or pass savePath() to save()."; + throw new RuntimeException(re); + } + } + + try { + OutputStream os = null; + + if (PApplet.javaVersion >= 1.4f) { + if (saveImageFormats == null) { + //saveImageFormats = javax.imageio.ImageIO.getWriterFormatNames(); + try { + Class ioClass = Class.forName("javax.imageio.ImageIO"); + Method getFormatNamesMethod = + ioClass.getMethod("getWriterFormatNames", (Class[]) null); + saveImageFormats = (String[]) + getFormatNamesMethod.invoke((Class[]) null, (Object[]) null); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (saveImageFormats != null) { + for (int i = 0; i < saveImageFormats.length; i++) { + if (path.endsWith("." + saveImageFormats[i])) { + saveImageIO(path); + return; + } + } + } + } + + if (path.toLowerCase().endsWith(".tga")) { + os = new BufferedOutputStream(new FileOutputStream(path), 32768); + success = saveTGA(os); //, pixels, width, height, format); + + } else { + if (!path.toLowerCase().endsWith(".tif") && + !path.toLowerCase().endsWith(".tiff")) { + // if no .tif extension, add it.. + path += ".tif"; + } + os = new BufferedOutputStream(new FileOutputStream(path), 32768); + success = saveTIFF(os); //, pixels, width, height); + } + os.flush(); + os.close(); + + } catch (IOException e) { + //System.err.println("Error while saving image."); + e.printStackTrace(); + success = false; + } + if (!success) { + throw new RuntimeException("Error while saving image."); + } + } +} + diff --git a/core/PLine.java b/core/PLine.java new file mode 100644 index 000000000..b69780a8a --- /dev/null +++ b/core/PLine.java @@ -0,0 +1,1295 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + */ + +package processing.core; + + +/** + * Code for rendering lines. + *

    + * This code will soon be removed. + * Written by Carlos Rocha. + */ +public class PLine implements PConstants +{ + private int[] m_pixels; + private float[] m_zbuffer; + //private int[] m_stencil; + + private int m_index; + + static final int R_COLOR = 0x1; + static final int R_ALPHA = 0x2; + static final int R_SPATIAL = 0x8; + static final int R_THICK = 0x4; + static final int R_SMOOTH = 0x10; + + private int SCREEN_WIDTH; + private int SCREEN_HEIGHT; + private int SCREEN_WIDTH1; + private int SCREEN_HEIGHT1; + + public boolean INTERPOLATE_RGB; + public boolean INTERPOLATE_ALPHA; + public boolean INTERPOLATE_Z; + public boolean INTERPOLATE_THICK; + + // antialias + private boolean SMOOTH; + + // blender + //private boolean BLENDER; + + // stroke color + private int m_stroke; + + // draw flags + public int m_drawFlags; + + // vertex coordinates + private float[] x_array; + private float[] y_array; + private float[] z_array; + + // vertex intensity + private float[] r_array; + private float[] g_array; + private float[] b_array; + private float[] a_array; + + // vertex offsets + private int o0; + private int o1; + + // start values + private float m_r0; + private float m_g0; + private float m_b0; + private float m_a0; + private float m_z0; + + // deltas + private float dz; + + // rgba deltas + private float dr; + private float dg; + private float db; + private float da; + + private PGraphics parent; + + + public PLine(PGraphics g) { + INTERPOLATE_Z = false; + + x_array = new float[2]; + y_array = new float[2]; + z_array = new float[2]; + r_array = new float[2]; + g_array = new float[2]; + b_array = new float[2]; + a_array = new float[2]; + + this.parent = g; + } + + + public void reset() { + // reset these in case PGraphics was resized + SCREEN_WIDTH = parent.width; + SCREEN_HEIGHT = parent.height; + SCREEN_WIDTH1 = SCREEN_WIDTH-1; + SCREEN_HEIGHT1 = SCREEN_HEIGHT-1; + + m_pixels = parent.pixels; + //m_stencil = parent.stencil; + m_zbuffer = parent.zbuffer; + + // other things to reset + + INTERPOLATE_RGB = false; + INTERPOLATE_ALPHA = false; + //INTERPOLATE_Z = false; + m_drawFlags = 0; + m_index = 0; + //BLENDER = false; + } + + + public void setVertices(float x0, float y0, float z0, + float x1, float y1, float z1) { + // [rocha] fixed z drawing, so whenever a line turns on + // z interpolation, all the lines are z interpolated + if (z0 != z1 || z0!=0.0f || z1!=0.0f || INTERPOLATE_Z) { + INTERPOLATE_Z = true; + m_drawFlags |= R_SPATIAL; + } else { + INTERPOLATE_Z = false; + m_drawFlags &= ~R_SPATIAL; + } + + z_array[0] = z0; + z_array[1] = z1; + + x_array[0] = x0; + x_array[1] = x1; + + y_array[0] = y0; + y_array[1] = y1; + } + + public void setIntensities(float r0, float g0, float b0, float a0, + float r1, float g1, float b1, float a1) { + a_array[0] = (a0 * 253f + 1.0f) * 65536f; + a_array[1] = (a1 * 253f + 1.0f) * 65536f; + + // check if we need alpha or not? + if ((a0 != 1.0f) || (a1 != 1.0f)) { + INTERPOLATE_ALPHA = true; + m_drawFlags |= R_ALPHA; + } else { + INTERPOLATE_ALPHA = false; + m_drawFlags &= ~R_ALPHA; + } + + // extra scaling added to prevent color "overflood" due to rounding errors + r_array[0] = (r0 * 253f + 1.0f) * 65536f; + r_array[1] = (r1 * 253f + 1.0f) * 65536f; + + g_array[0] = (g0 * 253f + 1.0f) * 65536f; + g_array[1] = (g1 * 253f + 1.0f) * 65536f; + + b_array[0] = (b0 * 253f + 1.0f) * 65536f; + b_array[1] = (b1 * 253f + 1.0f) * 65536f; + + // check if we need to interpolate the intensity values + if (r0 != r1) { + INTERPOLATE_RGB = true; + m_drawFlags |= R_COLOR; + + } else if (g0 != g1) { + INTERPOLATE_RGB = true; + m_drawFlags |= R_COLOR; + + } else if (b0 != b1) { + INTERPOLATE_RGB = true; + m_drawFlags |= R_COLOR; + + } else { + // when plain we use the stroke color of the first vertex + m_stroke = 0xFF000000 | + ((int)(255*r0) << 16) | ((int)(255*g0) << 8) | (int)(255*b0); + INTERPOLATE_RGB = false; + m_drawFlags &= ~R_COLOR; + } + } + + + public void setIndex(int index) { + m_index = index; + //BLENDER = false; + if (m_index != -1) { + //BLENDER = true; + } else { + m_index = 0; + } + } + + + public void draw() { + int xi; + int yi; + int length; + boolean visible = true; + + if (parent.smooth) { + SMOOTH = true; + m_drawFlags |= R_SMOOTH; + + } else { + SMOOTH = false; + m_drawFlags &= ~R_SMOOTH; + } + + // line hack + if (parent.hints[NO_FLYING_POO]) { + float nwidth2 = -SCREEN_WIDTH; + float nheight2 = -SCREEN_HEIGHT; + float width2 = SCREEN_WIDTH * 2; + float height2 = SCREEN_HEIGHT * 2; + if ((x_array[1] < nwidth2) || + (x_array[1] > width2) || + (x_array[0] < nwidth2) || + (x_array[0] > width2) || + (y_array[1] < nheight2) || + (y_array[1] > height2) || + (y_array[0] < nheight2) || + (y_array[0] > height2)) { + return; // this is a bad line + } + } + + /////////////////////////////////////// + // line clipping + visible = lineClipping(); + if (!visible) { + return; + } + + /////////////////////////////////////// + // calculate line values + int shortLen; + int longLen; + boolean yLonger; + int dt; + + yLonger = false; + + // HACK for drawing lines left-to-right for rev 0069 + // some kind of bug exists with the line-stepping algorithm + // that causes strange patterns in the anti-aliasing. + // [040228 fry] + // + // swap rgba as well as the coords.. oops + // [040712 fry] + // + if (x_array[1] < x_array[0]) { + float t; + + t = x_array[1]; x_array[1] = x_array[0]; x_array[0] = t; + t = y_array[1]; y_array[1] = y_array[0]; y_array[0] = t; + t = z_array[1]; z_array[1] = z_array[0]; z_array[0] = t; + + t = r_array[1]; r_array[1] = r_array[0]; r_array[0] = t; + t = g_array[1]; g_array[1] = g_array[0]; g_array[0] = t; + t = b_array[1]; b_array[1] = b_array[0]; b_array[0] = t; + t = a_array[1]; a_array[1] = a_array[0]; a_array[0] = t; + } + + // important - don't change the casts + // is needed this way for line drawing algorithm + longLen = (int)x_array[1] - (int)x_array[0]; + shortLen = (int)y_array[1] - (int)y_array[0]; + + if (Math.abs(shortLen) > Math.abs(longLen)) { + int swap = shortLen; + shortLen = longLen; + longLen = swap; + yLonger = true; + } + + // now we sort points so longLen is always positive + // and we always start drawing from x[0], y[0] + if (longLen < 0) { + // swap order + o0 = 1; + o1 = 0; + + xi = (int) x_array[1]; + yi = (int) y_array[1]; + + length = -longLen; + + } else { + o0 = 0; + o1 = 1; + + xi = (int) x_array[0]; + yi = (int) y_array[0]; + + length = longLen; + } + + // calculate dt + if (length == 0) { + dt = 0; + } else { + dt = (shortLen << 16) / longLen; + } + + m_r0 = r_array[o0]; + m_g0 = g_array[o0]; + m_b0 = b_array[o0]; + + if (INTERPOLATE_RGB) { + dr = (r_array[o1] - r_array[o0]) / length; + dg = (g_array[o1] - g_array[o0]) / length; + db = (b_array[o1] - b_array[o0]) / length; + } else { + dr = 0; + dg = 0; + db = 0; + } + + m_a0 = a_array[o0]; + + if (INTERPOLATE_ALPHA) { + da = (a_array[o1] - a_array[o0]) / length; + } else { + da = 0; + } + + m_z0 = z_array[o0]; + //z0 += -0.001f; // [rocha] ugly fix for z buffer precision + + if (INTERPOLATE_Z) { + dz = (z_array[o1] - z_array[o0]) / length; + } else { + dz = 0; + } + + // draw thin points + if (length == 0) { + if (INTERPOLATE_ALPHA) { + drawPoint_alpha(xi, yi); + } else { + drawPoint(xi, yi); + } + return; + } + + /* + // draw antialias polygon lines for non stroked polygons + if (BLENDER && SMOOTH) { + // fix for endpoints not being drawn + // [rocha] + drawPoint_alpha((int)x_array[0], (int)x_array[0]); + drawPoint_alpha((int)x_array[1], (int)x_array[1]); + + drawline_blender(x_array[0], y_array[0], x_array[1], y_array[1]); + return; + } + */ + + // draw normal strokes + if (SMOOTH) { + drawLine_smooth(xi, yi, dt, length, yLonger); + + } else { + if (m_drawFlags == 0) { + drawLine_plain(xi, yi, dt, length, yLonger); + + } else if (m_drawFlags == R_ALPHA) { + drawLine_plain_alpha(xi, yi, dt, length, yLonger); + + } else if (m_drawFlags == R_COLOR) { + drawLine_color(xi, yi, dt, length, yLonger); + + } else if (m_drawFlags == (R_COLOR + R_ALPHA)) { + drawLine_color_alpha(xi, yi, dt, length, yLonger); + + } else if (m_drawFlags == R_SPATIAL) { + drawLine_plain_spatial(xi, yi, dt, length, yLonger); + + } else if (m_drawFlags == (R_SPATIAL + R_ALPHA)) { + drawLine_plain_alpha_spatial(xi, yi, dt, length, yLonger); + + } else if (m_drawFlags == (R_SPATIAL + R_COLOR)) { + drawLine_color_spatial(xi, yi, dt, length, yLonger); + + } else if (m_drawFlags == (R_SPATIAL + R_COLOR + R_ALPHA)) { + drawLine_color_alpha_spatial(xi, yi, dt, length, yLonger); + } + } + } + + + public boolean lineClipping() { + // new cohen-sutherland clipping code, as old one was buggy [toxi] + // get the "dips" for the points to clip + int code1 = lineClipCode(x_array[0], y_array[0]); + int code2 = lineClipCode(x_array[1], y_array[1]); + int dip = code1 | code2; + + if ((code1 & code2)!=0) { + + return false; + + } else if (dip != 0) { + + // now calculate the clipped points + float a0 = 0, a1 = 1, a = 0; + + for (int i = 0; i < 4; i++) { + if (((dip>>i)%2)==1){ + a = lineSlope(x_array[0], y_array[0], x_array[1], y_array[1], i+1); + if (((code1 >> i) % 2) == 1) { + a0 = (a>a0)?a:a0; // max(a,a0) + } else { + a1 = (a a1) { + return false; + } else { + float xt = x_array[0]; + float yt = y_array[0]; + + x_array[0] = xt + a0 * (x_array[1] - xt); + y_array[0] = yt + a0 * (y_array[1] - yt); + x_array[1] = xt + a1 * (x_array[1] - xt); + y_array[1] = yt + a1 * (y_array[1] - yt); + + // interpolate remaining parameters + if (INTERPOLATE_RGB) { + float t = r_array[0]; + r_array[0] = t + a0 * (r_array[1] - t); + r_array[1] = t + a1 * (r_array[1] - t); + t = g_array[0]; + g_array[0] = t + a0 * (g_array[1] - t); + g_array[1] = t + a1 * (g_array[1] - t); + t = b_array[0]; + b_array[0] = t + a0 * (b_array[1] - t); + b_array[1] = t + a1 * (b_array[1] - t); + } + + if (INTERPOLATE_ALPHA) { + float t = a_array[0]; + a_array[0] = t + a0 * (a_array[1] - t); + a_array[1] = t + a1 * (a_array[1] - t); + } + } + } + return true; + } + + + private int lineClipCode(float xi, float yi) { + int xmin = 0; + int ymin = 0; + int xmax = SCREEN_WIDTH1; + int ymax = SCREEN_HEIGHT1; + + //return ((yi < ymin ? 8 : 0) | (yi > ymax ? 4 : 0) | + // (xi < xmin ? 2 : 0) | (xi > xmax ? 1 : 0)); + //(int) added by ewjordan 6/13/07 because otherwise we sometimes clip last pixel when it should actually be displayed. + //Currently the min values are okay because values less than 0 should not be rendered; however, bear in mind that + //(int) casts towards zero, so without this clipping, values between -1+eps and +1-eps would all be rendered as 0. + return ((yi < ymin ? 8 : 0) | ((int)yi > ymax ? 4 : 0) | + (xi < xmin ? 2 : 0) | ((int)xi > xmax ? 1 : 0)); + } + + + private float lineSlope(float x1, float y1, float x2, float y2, int border) { + int xmin = 0; + int ymin = 0; + int xmax = SCREEN_WIDTH1; + int ymax = SCREEN_HEIGHT1; + + switch (border) { + case 4: return (ymin-y1)/(y2-y1); + case 3: return (ymax-y1)/(y2-y1); + case 2: return (xmin-x1)/(x2-x1); + case 1: return (xmax-x1)/(x2-x1); + } + return -1f; + } + + + private void drawPoint(int x0, int y0) { + float iz = m_z0; + int offset = y0 * SCREEN_WIDTH + x0; + + if (iz <= m_zbuffer[offset]) { + m_pixels[offset] = m_stroke; + m_zbuffer[offset] = iz; + } + } + + + private void drawPoint_alpha(int x0, int y0) { + int ia = (int) a_array[0]; + int pr = m_stroke & 0xFF0000; + int pg = m_stroke & 0xFF00; + int pb = m_stroke & 0xFF; + float iz = m_z0; + int offset = y0 * SCREEN_WIDTH + x0; + + if (iz <= m_zbuffer[offset]) { + int alpha = ia >> 16; + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0 &= 0xFF0000; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + //m_zbuffer[offset] = iz; + } + } + + + private void drawLine_plain(int x0, int y0, int dt, + int length, boolean vertical) { + // new "extremely fast" line code + // adapted from http://www.edepot.com/linee.html + // first version modified by [toxi] + // simplified by [rocha] + // length must be >= 0 + + //assert length>=0:length; + + int offset = 0; + + if (vertical) { + // vertical + length += y0; + for (int j = 0x8000 + (x0<<16); y0 <= length; ++y0) { + offset = y0 * SCREEN_WIDTH + (j>>16); + m_pixels[offset] = m_stroke; + m_zbuffer[offset] = m_z0; + j+=dt; + } + + } else { + // horizontal + length += x0; + for (int j = 0x8000 + (y0<<16); x0 <= length; ++x0) { + offset = (j>>16) * SCREEN_WIDTH + x0; + m_pixels[offset] = m_stroke; + //m_zbuffer[offset] = m_z0; + j+=dt; + } + } + } + + + private void drawLine_plain_alpha(int x0, int y0, int dt, + int length, boolean vertical) { + int offset = 0; + + int pr = m_stroke & 0xFF0000; + int pg = m_stroke & 0xFF00; + int pb = m_stroke & 0xFF; + + int ia = (int) (m_a0); + + if (vertical) { + length += y0; + for (int j = 0x8000 + (x0<<16); y0 <= length; ++y0) { + offset = y0 * SCREEN_WIDTH + (j>>16); + + int alpha = ia >> 16; + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0 &= 0xFF0000; + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = m_z0; + + ia += da; + j += dt; + } + + } else { // horizontal + length += x0; + for (int j = 0x8000 + (y0<<16); x0 <= length; ++x0) { + offset = (j>>16) * SCREEN_WIDTH + x0; + + int alpha = ia >> 16; + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0&=0xFF0000; + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = m_z0; + + ia += da; + j += dt; + } + } + } + + + private void drawLine_color(int x0, int y0, int dt, + int length, boolean vertical) { + int offset = 0; + + int ir = (int) m_r0; + int ig = (int) m_g0; + int ib = (int) m_b0; + + if (vertical) { + length += y0; + for (int j = 0x8000 + (x0<<16); y0 <= length; ++y0) { + offset = y0 * SCREEN_WIDTH + (j>>16); + m_pixels[offset] = 0xFF000000 | + ((ir & 0xFF0000) | ((ig >> 8) & 0xFF00) | (ib >> 16)); + m_zbuffer[offset] = m_z0; + ir += dr; + ig += dg; + ib += db; + j +=dt; + } + + } else { // horizontal + length += x0; + for (int j = 0x8000 + (y0<<16); x0 <= length; ++x0) { + offset = (j>>16) * SCREEN_WIDTH + x0; + m_pixels[offset] = 0xFF000000 | + ((ir & 0xFF0000) | ((ig >> 8) & 0xFF00) | (ib >> 16)); + m_zbuffer[offset] = m_z0; + ir += dr; + ig += dg; + ib += db; + j += dt; + } + } + } + + + private void drawLine_color_alpha(int x0, int y0, int dt, + int length, boolean vertical) { + int offset = 0; + + int ir = (int) m_r0; + int ig = (int) m_g0; + int ib = (int) m_b0; + int ia = (int) m_a0; + + if (vertical) { + length += y0; + for (int j = 0x8000 + (x0<<16); y0 <= length; ++y0) { + offset = y0 * SCREEN_WIDTH + (j>>16); + + int pr = ir & 0xFF0000; + int pg = (ig >> 8) & 0xFF00; + int pb = (ib >> 16); + + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0&=0xFF0000; + + int alpha = ia >> 16; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = m_z0; + + ir+= dr; + ig+= dg; + ib+= db; + ia+= da; + j+=dt; + } + + } else { // horizontal + length += x0; + for (int j = 0x8000 + (y0<<16); x0 <= length; ++x0) { + offset = (j>>16) * SCREEN_WIDTH + x0; + + int pr = ir & 0xFF0000; + int pg = (ig >> 8) & 0xFF00; + int pb = (ib >> 16); + + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0&=0xFF0000; + + int alpha = ia >> 16; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = m_z0; + + ir+= dr; + ig+= dg; + ib+= db; + ia+= da; + j+=dt; + } + } + } + + + private void drawLine_plain_spatial(int x0, int y0, int dt, + int length, boolean vertical) { + int offset = 0; + float iz = m_z0; + + if (vertical) { + length += y0; + for (int j = 0x8000 + (x0<<16); y0 <= length; ++y0) { + offset = y0 * SCREEN_WIDTH + (j>>16); + if (iz <= m_zbuffer[offset]) { + m_pixels[offset] = m_stroke; + m_zbuffer[offset] = iz; + } + iz+=dz; + j+=dt; + } + + } else { // horizontal + length += x0; + for (int j = 0x8000 + (y0<<16); x0 <= length; ++x0) { + offset = (j>>16) * SCREEN_WIDTH + x0; + if (iz <= m_zbuffer[offset]) { + m_pixels[offset] = m_stroke; + m_zbuffer[offset] = iz; + } + iz+=dz; + j+=dt; + } + } + } + + + private void drawLine_plain_alpha_spatial(int x0, int y0, int dt, + int length, boolean vertical) { + int offset = 0; + float iz = m_z0; + + int pr = m_stroke & 0xFF0000; + int pg = m_stroke & 0xFF00; + int pb = m_stroke & 0xFF; + + int ia = (int) m_a0; + + if (vertical) { + length += y0; + for (int j = 0x8000 + (x0<<16); y0 <= length; ++y0) { + offset = y0 * SCREEN_WIDTH + (j>>16); + + if (iz <= m_zbuffer[offset]) { + int alpha = ia >> 16; + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0 &= 0xFF0000; + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + //m_zbuffer[offset] = iz; + } + iz +=dz; + ia += da; + j += dt; + } + + } else { // horizontal + length += x0; + for (int j = 0x8000 + (y0<<16); x0 <= length; ++x0) { + offset = (j>>16) * SCREEN_WIDTH + x0; + + if (iz <= m_zbuffer[offset]) { + int alpha = ia >> 16; + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0&=0xFF0000; + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + //m_zbuffer[offset] = iz; + } + iz += dz; + ia += da; + j += dt; + } + } + } + + + private void drawLine_color_spatial(int x0, int y0, int dt, + int length, boolean vertical) { + int offset = 0; + float iz = m_z0; + + int ir = (int) m_r0; + int ig = (int) m_g0; + int ib = (int) m_b0; + + if (vertical) { + length += y0; + for (int j = 0x8000 + (x0<<16); y0 <= length; ++y0) { + offset = y0 * SCREEN_WIDTH + (j>>16); + + if (iz <= m_zbuffer[offset]) { + m_pixels[offset] = 0xFF000000 | + ((ir & 0xFF0000) | ((ig >> 8) & 0xFF00) | (ib >> 16)); + m_zbuffer[offset] = iz; + } + iz +=dz; + ir += dr; + ig += dg; + ib += db; + j += dt; + } + } else { // horizontal + length += x0; + for (int j = 0x8000 + (y0<<16); x0 <= length; ++x0) { + offset = (j>>16) * SCREEN_WIDTH + x0; + if (iz <= m_zbuffer[offset]) { + m_pixels[offset] = 0xFF000000 | + ((ir & 0xFF0000) | ((ig >> 8) & 0xFF00) | (ib >> 16)); + m_zbuffer[offset] = iz; + } + iz += dz; + ir += dr; + ig += dg; + ib += db; + j += dt; + } + return; + } + } + + + private void drawLine_color_alpha_spatial(int x0, int y0, int dt, + int length, boolean vertical) { + int offset = 0; + float iz = m_z0; + + int ir = (int) m_r0; + int ig = (int) m_g0; + int ib = (int) m_b0; + int ia = (int) m_a0; + + if (vertical) { + length += y0; + for (int j = 0x8000 + (x0<<16); y0 <= length; ++y0) { + offset = y0 * SCREEN_WIDTH + (j>>16); + + if (iz <= m_zbuffer[offset]) { + int pr = ir & 0xFF0000; + int pg = (ig >> 8) & 0xFF00; + int pb = (ib >> 16); + + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0&=0xFF0000; + + int alpha = ia >> 16; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = iz; + } + iz+=dz; + ir+= dr; + ig+= dg; + ib+= db; + ia+= da; + j+=dt; + } + + } else { // horizontal + length += x0; + for (int j = 0x8000 + (y0<<16); x0 <= length; ++x0) { + offset = (j>>16) * SCREEN_WIDTH + x0; + + if (iz <= m_zbuffer[offset]) { + int pr = ir & 0xFF0000; + int pg = (ig >> 8) & 0xFF00; + int pb = (ib >> 16); + + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0 &= 0xFF0000; + + int alpha = ia >> 16; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = iz; + } + iz += dz; + ir += dr; + ig += dg; + ib += db; + ia += da; + j += dt; + } + } + } + + + void drawLine_smooth(int x0, int y0, int dt, + int length, boolean vertical) { + int xi, yi; // these must be >=32 bits + int offset = 0; + int temp; + int end; + + float iz = m_z0; + + int ir = (int) m_r0; + int ig = (int) m_g0; + int ib = (int) m_b0; + int ia = (int) m_a0; + + if (vertical) { + xi = x0 << 16; + yi = y0 << 16; + + end = length + y0; + + while ((yi >> 16) < end) { + + offset = (yi>>16) * SCREEN_WIDTH + (xi>>16); + + int pr = ir & 0xFF0000; + int pg = (ig >> 8) & 0xFF00; + int pb = (ib >> 16); + + if (iz <= m_zbuffer[offset]) { + int alpha = (((~xi >> 8) & 0xFF) * (ia >> 16)) >> 8; + + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0&=0xFF0000; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = iz; + } + + // this if() makes things slow. there shoudl be + // a better way to check if the second pixel is + // withing the image array [rocha] + temp = ((xi>>16)+1); + if (temp >= SCREEN_WIDTH) { + xi += dt; + yi += (1 << 16); + continue; + } + + offset = (yi>>16) * SCREEN_WIDTH + temp; + + if (iz <= m_zbuffer[offset]) { + int alpha = (((xi >> 8) & 0xFF) * (ia >> 16)) >> 8; + + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0 &= 0xFF0000; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = iz; + } + + xi += dt; + yi += (1 << 16); + + iz+=dz; + ir+= dr; + ig+= dg; + ib+= db; + ia+= da; + } + + } else { // horizontal + xi = x0 << 16; + yi = y0 << 16; + end = length + x0; + + while ((xi >> 16) < end) { + offset = (yi>>16) * SCREEN_WIDTH + (xi>>16); + + int pr = ir & 0xFF0000; + int pg = (ig >> 8) & 0xFF00; + int pb = (ib >> 16); + + if (iz <= m_zbuffer[offset]) { + int alpha = (((~yi >> 8) & 0xFF) * (ia >> 16)) >> 8; + + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0 &= 0xFF0000; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = iz; + } + + // see above [rocha] + temp = ((yi>>16)+1); + if (temp >= SCREEN_HEIGHT) { + xi += (1 << 16); + yi += dt; + continue; + } + + offset = temp * SCREEN_WIDTH + (xi>>16); + + if (iz <= m_zbuffer[offset]) { + int alpha = (((yi >> 8) & 0xFF) * (ia >> 16)) >> 8; + + int r0 = m_pixels[offset]; + int g0 = r0 & 0xFF00; + int b0 = r0 & 0xFF; + r0&=0xFF0000; + + r0 = r0 + (((pr - r0) * alpha) >> 8); + g0 = g0 + (((pg - g0) * alpha) >> 8); + b0 = b0 + (((pb - b0) * alpha) >> 8); + + m_pixels[offset] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + m_zbuffer[offset] = iz; + } + + xi += (1 << 16); + yi += dt; + + iz+=dz; + ir+= dr; + ig+= dg; + ib+= db; + ia+= da; + } + } + } + + + /** + * Special "blender" line code by sami, + * used for anti-aliasing polygon edges + */ + /* + private void drawline_blender(double x0, double y0, double x1, double y1) + { + double tmp; + double dx = x1-x0; + double dy = y1-y0; + double adx = (dx >= 0) ? dx : -dx; + double ady = (dy >= 0) ? dy : -dy; + + // VERY small line --> skip + if (adx < 0.0001d && ady < 0.0001d) + return; + + // pixel color + int pxl; + + // vaakaviiva + if (adx > ady) { + // flip if x0 > x1 + if (x0 > x1) { + tmp = x0; + x0 = x1; + x1 = tmp; + tmp = y0; + y0 = y1; + y1 = tmp; + dx = x1-x0; + dy = y1-y0; + } + + // add interpolation params here + double addy = dy / dx; + + int ix0 = (int) (x0 + PIXEL_CENTER); + if (ix0 < 0) + ix0 = 0; + + int ix1 = (int) (x1 + PIXEL_CENTER); + if (ix1 > SCREEN_WIDTH) + ix1 = SCREEN_WIDTH; + + double delta = (ix0 + PIXEL_CENTER) - x0; + double ys = y0 + delta * addy; + + for (int a = ix0; a < ix1; a++,ys+=addy) { + int iy = (int) (ys - PIXEL_CENTER); + if ((iy >= 0) && (iy < SCREEN_HEIGHT1)) { + int ofs1 = iy * SCREEN_WIDTH + a; + int ofs2 = ofs1 + SCREEN_WIDTH; + + if (m_stencil[ofs1] == m_index) { + pxl = m_pixels[ofs1]; + } else if (m_stencil[ofs2] == m_index) { + pxl = m_pixels[ofs2]; + } else { + //m_pixels[ofs1] = 0xFFFFFF; + //m_pixels[ofs2] = 0xFFFFFF; + continue; + } + + double frcf = ys - PIXEL_CENTER; + + int frac1 = ((int) (frcf * 256f) & 0xFF); + int frac2 = 255 - frac1; + int pr = (pxl & 0xFF0000); + int pg = (pxl & 0xFF00); + int pb = (pxl & 0xFF); + + int r0 = m_pixels[ofs1]; + int g0 = (r0 & 0xFF00); + int b0 = (r0 & 0xFF); + r0 = (r0 & 0xFF0000); + r0 = r0 + (((pr - r0) * frac2) >> 8); + g0 = g0 + (((pg - g0) * frac2) >> 8); + b0 = b0 + (((pb - b0) * frac2) >> 8); + m_pixels[ofs1] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + + r0 = m_pixels[ofs2]; + g0 = (r0 & 0xFF00); + b0 = (r0 & 0xFF); + r0 = (r0 & 0xFF0000); + r0 = r0 + (((pr - r0) * frac1) >> 8); + g0 = g0 + (((pg - g0) * frac1) >> 8); + b0 = b0 + (((pb - b0) * frac1) >> 8); + m_pixels[ofs2] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + + //m_pixels[ofs1] = 0xFF00FF; + //m_pixels[ofs2] = 0xFFFF00; + } + } + } else { // pystyviiva + // flip if y1 > y0 + if (y0 > y1) { + tmp = x0; + x0 = x1; + x1 = tmp; + tmp = y0; + y0 = y1; + y1 = tmp; + dx = x1-x0; + dy = y1-y0; + } + + double addx = dx / dy; + int iy0 = (int) (y0 + PIXEL_CENTER); + if (iy0 < 0) + iy0 = 0; + int iy1 = (int) (y1 + PIXEL_CENTER); + if (iy1 > SCREEN_HEIGHT) + iy1 = SCREEN_HEIGHT; + + double delta = (iy0 + PIXEL_CENTER) - y0; + double xs = x0 + delta * addx; + + iy0*=SCREEN_WIDTH; + iy1*=SCREEN_WIDTH; + for (int a = iy0; a < iy1; a+=SCREEN_WIDTH,xs+=addx) { + int ix = (int) (xs - PIXEL_CENTER); + if ((ix >= 0) && (ix < SCREEN_WIDTH1)) { + int ofs1 = a + ix; + int ofs2 = ofs1+1; + + if (m_stencil[ofs1] == m_index) { + pxl = m_pixels[ofs1]; + } else if (m_stencil[ofs2] == m_index) { + pxl = m_pixels[ofs2]; + } else { + //m_pixels[ofs1] = 0xFFFFFF; + //m_pixels[ofs2] = 0xFFFFFF; + continue; + } + + int pr = (pxl & 0xFF0000); + int pg = (pxl & 0xFF00); + int pb = (pxl & 0xFF); + + double frcf = xs - PIXEL_CENTER; + int frac1 = ((int) (frcf * 256f) & 0xFF); + int frac2 = 255 - frac1; + + int r0 = m_pixels[ofs1]; + int g0 = (r0 & 0xFF00); + int b0 = (r0 & 0xFF); + r0 = (r0 & 0xFF0000); + r0 = r0 + (((pr - r0) * frac2) >> 8); + g0 = g0 + (((pg - g0) * frac2) >> 8); + b0 = b0 + (((pb - b0) * frac2) >> 8); + m_pixels[ofs1] = 0xFF000000 | + (r0 & 0xFF0000) | (g0 & 0xFF00) | (b0 & 0xFF); + + r0 = m_pixels[ofs2]; + g0 = (r0 & 0xFF00); + b0 = (r0 & 0xFF); + r0 = (r0 & 0xFF0000); + r0 = r0 + (((pr - r0) * frac1) >> 8); + g0 = g0 + (((pg - g0) * frac1) >> 8); + b0 = b0 + (((pb - b0) * frac1) >> 8); + + //m_pixels[ofs1] = 0x0000FF; + //m_pixels[ofs2] = 0x00FFFF; + } + } + } + } + */ +} diff --git a/core/PMatrix.java b/core/PMatrix.java new file mode 100644 index 000000000..9b19da613 --- /dev/null +++ b/core/PMatrix.java @@ -0,0 +1,640 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://Proce55ing.net + + Copyright (c) 2005-06 Ben Fry and Casey Reas + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + + +/** + * 4x4 matrix implementation. + */ +public final class PMatrix implements PConstants { + + public float m00, m01, m02, m03; + public float m10, m11, m12, m13; + public float m20, m21, m22, m23; + public float m30, m31, m32, m33; + + final static int DEFAULT_STACK_DEPTH = 0; + int maxStackDepth; + int stackPointer = 0; + float stack[][]; + + + // locally allocated version to avoid creating new memory + static protected PMatrix inverseCopy; + + + public PMatrix() { + set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + maxStackDepth = DEFAULT_STACK_DEPTH; + } + + + public PMatrix(int stackDepth) { + set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + stack = new float[stackDepth][16]; + maxStackDepth = stackDepth; + } + + + public PMatrix(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) { + set(m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33); + maxStackDepth = DEFAULT_STACK_DEPTH; + } + + + // Make a copy of a matrix. We copy the stack depth, + // but we don't make a copy of the stack or the stack pointer. + public PMatrix(PMatrix src) { + set(src); + maxStackDepth = src.maxStackDepth; + stack = new float[maxStackDepth][16]; + } + + + public void reset() { + set(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + + public void clearStack() { + stackPointer = 0; + } + + + public boolean push() { + if (stackPointer == maxStackDepth) return false; + + stack[stackPointer][0] = m00; + stack[stackPointer][1] = m01; + stack[stackPointer][2] = m02; + stack[stackPointer][3] = m03; + + stack[stackPointer][4] = m10; + stack[stackPointer][5] = m11; + stack[stackPointer][6] = m12; + stack[stackPointer][7] = m13; + + stack[stackPointer][8] = m20; + stack[stackPointer][9] = m21; + stack[stackPointer][10] = m22; + stack[stackPointer][11] = m23; + + stack[stackPointer][12] = m30; + stack[stackPointer][13] = m31; + stack[stackPointer][14] = m32; + stack[stackPointer][15] = m33; + + stackPointer++; + return true; + } + + + public boolean pop() { + if (stackPointer == 0) return false; + stackPointer--; + + m00 = stack[stackPointer][0]; + m01 = stack[stackPointer][1]; + m02 = stack[stackPointer][2]; + m03 = stack[stackPointer][3]; + + m10 = stack[stackPointer][4]; + m11 = stack[stackPointer][5]; + m12 = stack[stackPointer][6]; + m13 = stack[stackPointer][7]; + + m20 = stack[stackPointer][8]; + m21 = stack[stackPointer][9]; + m22 = stack[stackPointer][10]; + m23 = stack[stackPointer][11]; + + m30 = stack[stackPointer][12]; + m31 = stack[stackPointer][13]; + m32 = stack[stackPointer][14]; + m33 = stack[stackPointer][15]; + + return true; + } + + + public void set(PMatrix src) { + set(src.m00, src.m01, src.m02, src.m03, + src.m10, src.m11, src.m12, src.m13, + src.m20, src.m21, src.m22, src.m23, + src.m30, src.m31, src.m32, src.m33); + } + + + public void set(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) { + this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m03 = m03; + this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m13 = m13; + this.m20 = m20; this.m21 = m21; this.m22 = m22; this.m23 = m23; + this.m30 = m30; this.m31 = m31; this.m32 = m32; this.m33 = m33; + } + + + public void translate(float tx, float ty) { + translate(tx, ty, 0); + } + + public void invTranslate(float tx, float ty) { + invTranslate(tx, ty, 0); + } + + + public void translate(float tx, float ty, float tz) { + m03 += tx*m00 + ty*m01 + tz*m02; + m13 += tx*m10 + ty*m11 + tz*m12; + m23 += tx*m20 + ty*m21 + tz*m22; + m33 += tx*m30 + ty*m31 + tz*m32; + } + + public void invTranslate(float tx, float ty, float tz) { + preApply(1, 0, 0, -tx, + 0, 1, 0, -ty, + 0, 0, 1, -tz, + 0, 0, 0, 1); + } + + + // OPT could save several multiplies for the 0s and 1s by just + // putting the multMatrix code here and removing uneccessary terms + + public void rotateX(float angle) { + float c = cos(angle); + float s = sin(angle); + apply(1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1); + } + + + public void invRotateX(float angle) { + float c = cos(-angle); + float s = sin(-angle); + preApply(1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1); + } + + + public void rotateY(float angle) { + float c = cos(angle); + float s = sin(angle); + apply(c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1); + } + + + public void invRotateY(float angle) { + float c = cos(-angle); + float s = sin(-angle); + preApply(c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1); + } + + + /** + * Just calls rotateZ because two dimensional rotation + * is the same as rotating along the z-axis. + */ + public void rotate(float angle) { + rotateZ(angle); + } + + + public void invRotate(float angle) { + invRotateZ(angle); + } + + + public void rotateZ(float angle) { + float c = cos(angle); + float s = sin(angle); + apply(c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + + public void invRotateZ(float angle) { + float c = cos(-angle); + float s = sin(-angle); + preApply(c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + + public void rotate(float angle, float v0, float v1, float v2) { + // should be in radians (i think), instead of degrees (gl uses degrees) + // based on 15-463 code, but similar to opengl ref p.443 + + // TODO should make sure this vector is normalized + + float c = cos(angle); + float s = sin(angle); + float t = 1.0f - c; + + apply((t*v0*v0) + c, (t*v0*v1) - (s*v2), (t*v0*v2) + (s*v1), 0, + (t*v0*v1) + (s*v2), (t*v1*v1) + c, (t*v1*v2) - (s*v0), 0, + (t*v0*v2) - (s*v1), (t*v1*v2) + (s*v0), (t*v2*v2) + c, 0, + 0, 0, 0, 1); + } + + + public void invRotate(float angle, float v0, float v1, float v2) { + // TODO should make sure this vector is normalized + + float c = cos(-angle); + float s = sin(-angle); + float t = 1.0f - c; + + preApply((t*v0*v0) + c, (t*v0*v1) - (s*v2), (t*v0*v2) + (s*v1), 0, + (t*v0*v1) + (s*v2), (t*v1*v1) + c, (t*v1*v2) - (s*v0), 0, + (t*v0*v2) - (s*v1), (t*v1*v2) + (s*v0), (t*v2*v2) + c, 0, + 0, 0, 0, 1); + } + + + public void scale(float s) { + apply(s, 0, 0, 0, 0, s, 0, 0, 0, 0, s, 0, 0, 0, 0, 1); + } + + + public void invScale(float s) { + preApply(1/s, 0, 0, 0, 0, 1/s, 0, 0, 0, 0, 1/s, 0, 0, 0, 0, 1); + } + + + public void scale(float sx, float sy) { + apply(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + + public void invScale(float sx, float sy) { + preApply(1/sx, 0, 0, 0, 0, 1/sy, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + + // OPTIMIZE: same as above + public void scale(float x, float y, float z) { + apply(x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1); + } + + + public void invScale(float x, float y, float z) { + preApply(1/x, 0, 0, 0, 0, 1/y, 0, 0, 0, 0, 1/z, 0, 0, 0, 0, 1); + } + + + /* + public void transform(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + apply(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + } + */ + + + public void preApply(PMatrix lhs) { + preApply(lhs.m00, lhs.m01, lhs.m02, lhs.m03, + lhs.m10, lhs.m11, lhs.m12, lhs.m13, + lhs.m20, lhs.m21, lhs.m22, lhs.m23, + lhs.m30, lhs.m31, lhs.m32, lhs.m33); + } + + + // for inverse operations, like multiplying the matrix on the left + public void preApply(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + + float r00 = n00*m00 + n01*m10 + n02*m20 + n03*m30; + float r01 = n00*m01 + n01*m11 + n02*m21 + n03*m31; + float r02 = n00*m02 + n01*m12 + n02*m22 + n03*m32; + float r03 = n00*m03 + n01*m13 + n02*m23 + n03*m33; + + float r10 = n10*m00 + n11*m10 + n12*m20 + n13*m30; + float r11 = n10*m01 + n11*m11 + n12*m21 + n13*m31; + float r12 = n10*m02 + n11*m12 + n12*m22 + n13*m32; + float r13 = n10*m03 + n11*m13 + n12*m23 + n13*m33; + + float r20 = n20*m00 + n21*m10 + n22*m20 + n23*m30; + float r21 = n20*m01 + n21*m11 + n22*m21 + n23*m31; + float r22 = n20*m02 + n21*m12 + n22*m22 + n23*m32; + float r23 = n20*m03 + n21*m13 + n22*m23 + n23*m33; + + float r30 = n30*m00 + n31*m10 + n32*m20 + n33*m30; + float r31 = n30*m01 + n31*m11 + n32*m21 + n33*m31; + float r32 = n30*m02 + n31*m12 + n32*m22 + n33*m32; + float r33 = n30*m03 + n31*m13 + n32*m23 + n33*m33; + + m00 = r00; m01 = r01; m02 = r02; m03 = r03; + m10 = r10; m11 = r11; m12 = r12; m13 = r13; + m20 = r20; m21 = r21; m22 = r22; m23 = r23; + m30 = r30; m31 = r31; m32 = r32; m33 = r33; + } + + + public boolean invApply(PMatrix rhs) { + PMatrix copy = new PMatrix(rhs); + PMatrix inverse = copy.invert(); + if (inverse == null) return false; + preApply(inverse); + return true; + } + + + public boolean invApply(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + if (inverseCopy == null) { + inverseCopy = new PMatrix(); + } + inverseCopy.set(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + PMatrix inverse = inverseCopy.invert(); + if (inverse == null) return false; + preApply(inverse); + return true; + } + + + public void apply(PMatrix rhs) { + apply(rhs.m00, rhs.m01, rhs.m02, rhs.m03, + rhs.m10, rhs.m11, rhs.m12, rhs.m13, + rhs.m20, rhs.m21, rhs.m22, rhs.m23, + rhs.m30, rhs.m31, rhs.m32, rhs.m33); + } + + + public void apply(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + + float r00 = m00*n00 + m01*n10 + m02*n20 + m03*n30; + float r01 = m00*n01 + m01*n11 + m02*n21 + m03*n31; + float r02 = m00*n02 + m01*n12 + m02*n22 + m03*n32; + float r03 = m00*n03 + m01*n13 + m02*n23 + m03*n33; + + float r10 = m10*n00 + m11*n10 + m12*n20 + m13*n30; + float r11 = m10*n01 + m11*n11 + m12*n21 + m13*n31; + float r12 = m10*n02 + m11*n12 + m12*n22 + m13*n32; + float r13 = m10*n03 + m11*n13 + m12*n23 + m13*n33; + + float r20 = m20*n00 + m21*n10 + m22*n20 + m23*n30; + float r21 = m20*n01 + m21*n11 + m22*n21 + m23*n31; + float r22 = m20*n02 + m21*n12 + m22*n22 + m23*n32; + float r23 = m20*n03 + m21*n13 + m22*n23 + m23*n33; + + float r30 = m30*n00 + m31*n10 + m32*n20 + m33*n30; + float r31 = m30*n01 + m31*n11 + m32*n21 + m33*n31; + float r32 = m30*n02 + m31*n12 + m32*n22 + m33*n32; + float r33 = m30*n03 + m31*n13 + m32*n23 + m33*n33; + + m00 = r00; m01 = r01; m02 = r02; m03 = r03; + m10 = r10; m11 = r11; m12 = r12; m13 = r13; + m20 = r20; m21 = r21; m22 = r22; m23 = r23; + m30 = r30; m31 = r31; m32 = r32; m33 = r33; + } + + + public void mult3(float vec[], float out[]) { + // must use these temp vars because vec may be the same as out + float tmpx = m00*vec[0] + m01*vec[1] + m02*vec[2] + m03; + float tmpy = m10*vec[0] + m11*vec[1] + m12*vec[2] + m13; + float tmpz = m20*vec[0] + m21*vec[1] + m22*vec[2] + m23; + + out[0] = tmpx; + out[1] = tmpy; + out[2] = tmpz; + } + + + public void mult(float vec[], float out[]) { + // must use these temp vars because vec may be the same as out + float tmpx = m00*vec[0] + m01*vec[1] + m02*vec[2] + m03*vec[3]; + float tmpy = m10*vec[0] + m11*vec[1] + m12*vec[2] + m13*vec[3]; + float tmpz = m20*vec[0] + m21*vec[1] + m22*vec[2] + m23*vec[3]; + float tmpw = m30*vec[0] + m31*vec[1] + m32*vec[2] + m33*vec[3]; + + out[0] = tmpx; + out[1] = tmpy; + out[2] = tmpz; + out[3] = tmpw; + } + + + /** + * @return the determinant of the matrix + */ + public float determinant() { + float f = + m00 + * ((m11 * m22 * m33 + m12 * m23 * m31 + m13 * m21 * m32) + - m13 * m22 * m31 + - m11 * m23 * m32 + - m12 * m21 * m33); + f -= m01 + * ((m10 * m22 * m33 + m12 * m23 * m30 + m13 * m20 * m32) + - m13 * m22 * m30 + - m10 * m23 * m32 + - m12 * m20 * m33); + f += m02 + * ((m10 * m21 * m33 + m11 * m23 * m30 + m13 * m20 * m31) + - m13 * m21 * m30 + - m10 * m23 * m31 + - m11 * m20 * m33); + f -= m03 + * ((m10 * m21 * m32 + m11 * m22 * m30 + m12 * m20 * m31) + - m12 * m21 * m30 + - m10 * m22 * m31 + - m11 * m20 * m32); + return f; + } + + + /** + * Calculate the determinant of a 3x3 matrix + * @return result + */ + private float determinant3x3(float t00, float t01, float t02, + float t10, float t11, float t12, + float t20, float t21, float t22) { + return (t00 * (t11 * t22 - t12 * t21) + + t01 * (t12 * t20 - t10 * t22) + + t02 * (t10 * t21 - t11 * t20)); + } + + + public PMatrix transpose() { + float temp; + temp = m01; m01 = m10; m10 = temp; + temp = m02; m02 = m20; m20 = temp; + temp = m03; m03 = m30; m30 = temp; + temp = m12; m12 = m21; m21 = temp; + temp = m13; m13 = m31; m31 = temp; + temp = m23; m23 = m32; m32 = temp; + return this; + } + + + /** + * Invert this matrix + * @return this if successful, null otherwise + */ + public PMatrix invert() { + + float determinant = determinant(); + + if (determinant != 0) { + // m00 m01 m02 m03 + // m10 m11 m12 m13 + // m20 m21 m22 m23 + // m30 m31 m32 m33 + float determinant_inv = 1f / determinant; + + // first row + float t00 = determinant3x3(m11, m12, m13, m21, m22, m23, m31, m32, m33); + float t01 = -determinant3x3(m10, m12, m13, m20, m22, m23, m30, m32, m33); + float t02 = determinant3x3(m10, m11, m13, m20, m21, m23, m30, m31, m33); + float t03 = -determinant3x3(m10, m11, m12, m20, m21, m22, m30, m31, m32); + + // second row + float t10 = -determinant3x3(m01, m02, m03, m21, m22, m23, m31, m32, m33); + float t11 = determinant3x3(m00, m02, m03, m20, m22, m23, m30, m32, m33); + float t12 = -determinant3x3(m00, m01, m03, m20, m21, m23, m30, m31, m33); + float t13 = determinant3x3(m00, m01, m02, m20, m21, m22, m30, m31, m32); + + // third row + float t20 = determinant3x3(m01, m02, m03, m11, m12, m13, m31, m32, m33); + float t21 = -determinant3x3(m00, m02, m03, m10, m12, m13, m30, m32, m33); + float t22 = determinant3x3(m00, m01, m03, m10, m11, m13, m30, m31, m33); + float t23 = -determinant3x3(m00, m01, m02, m10, m11, m12, m30, m31, m32); + + // fourth row + float t30 = -determinant3x3(m01, m02, m03, m11, m12, m13, m21, m22, m23); + float t31 = determinant3x3(m00, m02, m03, m10, m12, m13, m20, m22, m23); + float t32 = -determinant3x3(m00, m01, m03, m10, m11, m13, m20, m21, m23); + float t33 = determinant3x3(m00, m01, m02, m10, m11, m12, m20, m21, m22); + + // transpose and divide by the determinant + m00 = t00*determinant_inv; + m11 = t11*determinant_inv; + m22 = t22*determinant_inv; + m33 = t33*determinant_inv; + m01 = t10*determinant_inv; + m10 = t01*determinant_inv; + m20 = t02*determinant_inv; + m02 = t20*determinant_inv; + m12 = t21*determinant_inv; + m21 = t12*determinant_inv; + m03 = t30*determinant_inv; + m30 = t03*determinant_inv; + m13 = t31*determinant_inv; + m31 = t13*determinant_inv; + m32 = t23*determinant_inv; + m23 = t32*determinant_inv; + return this; + } + return null; + } + + + ////////////////////////////////////////////////////////////// + + + public void print() { + int big = (int) Math.abs(max(max(max(max(abs(m00), abs(m01)), + max(abs(m02), abs(m03))), + max(max(abs(m10), abs(m11)), + max(abs(m12), abs(m13)))), + max(max(max(abs(m20), abs(m21)), + max(abs(m22), abs(m23))), + max(max(abs(m30), abs(m31)), + max(abs(m32), abs(m33)))))); + + // avoid infinite loop + if (Float.isNaN(big) || Float.isInfinite(big)) { + big = 1000000; // set to something arbitrary + } + + int d = 1; + while ((big /= 10) != 0) d++; // cheap log() + + System.out.println(PApplet.nfs(m00, d, 4) + " " + + PApplet.nfs(m01, d, 4) + " " + + PApplet.nfs(m02, d, 4) + " " + + PApplet.nfs(m03, d, 4)); + + System.out.println(PApplet.nfs(m10, d, 4) + " " + + PApplet.nfs(m11, d, 4) + " " + + PApplet.nfs(m12, d, 4) + " " + + PApplet.nfs(m13, d, 4)); + + System.out.println(PApplet.nfs(m20, d, 4) + " " + + PApplet.nfs(m21, d, 4) + " " + + PApplet.nfs(m22, d, 4) + " " + + PApplet.nfs(m23, d, 4)); + + System.out.println(PApplet.nfs(m30, d, 4) + " " + + PApplet.nfs(m31, d, 4) + " " + + PApplet.nfs(m32, d, 4) + " " + + PApplet.nfs(m33, d, 4)); + + System.out.println(); + } + + + ////////////////////////////////////////////////////////////// + + + private final float max(float a, float b) { + return (a > b) ? a : b; + } + + private final float abs(float a) { + return (a < 0) ? -a : a; + } + + private final float sin(float angle) { + return (float)Math.sin(angle); + } + + private final float cos(float angle) { + return (float)Math.cos(angle); + } +} diff --git a/core/PPolygon.java b/core/PPolygon.java new file mode 100644 index 000000000..4c355dde7 --- /dev/null +++ b/core/PPolygon.java @@ -0,0 +1,772 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + + +/** + * zbuffer polygon rendering object for PGraphics. + *

    + * Likely to be removed before 1.0 as it's no longer particularly used. + */ +public class PPolygon implements PConstants { + + // identical to the constants from PGraphics + + static final int X = 0; // transformed xyzw + static final int Y = 1; // formerly SX SY SZ + static final int Z = 2; + + static final int R = 3; // actual rgb, after lighting + static final int G = 4; // fill stored here, transform in place + static final int B = 5; + static final int A = 6; + + static final int U = 7; // texture + static final int V = 8; + + // + + static final int DEFAULT_SIZE = 64; // this is needed for spheres + float vertices[][] = new float[DEFAULT_SIZE][PGraphics.VERTEX_FIELD_COUNT]; + int vertexCount; + + // really this is "debug" but.. + static final boolean FRY = false; + + // after some fiddling, this seems to produce the best results + //static final int ZBUFFER_MIN_COVERAGE = 204; + + float r[] = new float[DEFAULT_SIZE]; // storage used by incrementalize + float dr[] = new float[DEFAULT_SIZE]; + float l[] = new float[DEFAULT_SIZE]; // more storage for incrementalize + float dl[] = new float[DEFAULT_SIZE]; + float sp[] = new float[DEFAULT_SIZE]; // temporary storage for scanline + float sdp[] = new float[DEFAULT_SIZE]; + + // color and xyz are always interpolated + boolean interpX; + boolean interpZ; + boolean interpUV; // is this necessary? could just check timage != null + boolean interpARGB; + + int rgba; + int r2, g2, b2, a2, a2orig; + + boolean noDepthTest; + + PGraphics parent; + int pixels[]; + + // the parent's width/height, + // or if smooth is enabled, parent's w/h scaled + // up by the smooth dimension + int width, height; + int width1, height1; + + PImage timage; + int tpixels[]; + int theight, twidth; + int theight1, twidth1; + int tformat; + + // temp fix to behave like SMOOTH_IMAGES + boolean texture_smooth; + + // for anti-aliasing + static final int SUBXRES = 8; + static final int SUBXRES1 = 7; + static final int SUBYRES = 8; + static final int SUBYRES1 = 7; + static final int MAX_COVERAGE = SUBXRES * SUBYRES; + + boolean smooth; + int firstModY; + int lastModY; + int lastY; + int aaleft[] = new int[SUBYRES]; + int aaright[] = new int[SUBYRES]; + int aaleftmin, aarightmin; + int aaleftmax, aarightmax; + int aaleftfull, aarightfull; + + final private int MODYRES(int y) { + return (y & SUBYRES1); + } + + + public PPolygon(PGraphics iparent) { + parent = iparent; + reset(0); + } + + + public void reset(int count) { + vertexCount = count; + interpX = true; + interpZ = true; + interpUV = false; + interpARGB = true; + timage = null; + } + + + public float[] nextVertex() { + if (vertexCount == vertices.length) { + //parent.message(CHATTER, "re-allocating for " + + // (vertexCount*2) + " vertices"); + float temp[][] = new float[vertexCount<<1][PGraphics.VERTEX_FIELD_COUNT]; + System.arraycopy(vertices, 0, temp, 0, vertexCount); + vertices = temp; + + r = new float[vertices.length]; + dr = new float[vertices.length]; + l = new float[vertices.length]; + dl = new float[vertices.length]; + sp = new float[vertices.length]; + sdp = new float[vertices.length]; + } + return vertices[vertexCount++]; // returns v[0], sets vc to 1 + } + + + /** + * Return true if this vertex is redundant. If so, will also + * decrement the vertex count. + */ + /* + public boolean redundantVertex(float x, float y, float z) { + // because vertexCount will be 2 when setting vertex[1] + if (vertexCount < 2) return false; + + // vertexCount-1 is the current vertex that would be used + // vertexCount-2 would be the previous feller + if ((Math.abs(vertices[vertexCount-2][MX] - x) < EPSILON) && + (Math.abs(vertices[vertexCount-2][MY] - y) < EPSILON) && + (Math.abs(vertices[vertexCount-2][MZ] - z) < EPSILON)) { + vertexCount--; + return true; + } + return false; + } + */ + + + public void texture(PImage image) { + this.timage = image; + this.tpixels = image.pixels; + this.twidth = image.width; + this.theight = image.height; + this.tformat = image.format; + + twidth1 = twidth - 1; + theight1 = theight - 1; + interpUV = true; + } + + + public void render() { + if (vertexCount < 3) return; + + // these may have changed due to a resize() + // so they should be refreshed here + pixels = parent.pixels; + //zbuffer = parent.zbuffer; + + noDepthTest = parent.hints[DISABLE_DEPTH_TEST]; + smooth = parent.smooth; + + // by default, text turns on smooth for the textures + // themselves. but this should be shut off if the hint + // for DISABLE_TEXT_SMOOTH is set. + texture_smooth = (//parent.drawing_text && + !parent.hints[DISABLE_TEXT_SMOOTH]); + + width = smooth ? parent.width*SUBXRES : parent.width; + height = smooth ? parent.height*SUBYRES : parent.height; + + width1 = width - 1; + height1 = height - 1; + + if (!interpARGB) { + r2 = (int) (vertices[0][R] * 255); + g2 = (int) (vertices[0][G] * 255); + b2 = (int) (vertices[0][B] * 255); + a2 = (int) (vertices[0][A] * 255); + a2orig = a2; // save an extra copy + rgba = 0xff000000 | (r2 << 16) | (g2 << 8) | b2; + } + + for (int i = 0; i < vertexCount; i++) { + r[i] = 0; dr[i] = 0; l[i] = 0; dl[i] = 0; + } + + // hack to not make polygons fly into the screen + if (parent.hints[NO_FLYING_POO]) { + float nwidth2 = -width * 2; + float nheight2 = -height * 2; + float width2 = width * 2; + float height2 = height * 2; + for (int i = 0; i < vertexCount; i++) { + if ((vertices[i][X] < nwidth2) || + (vertices[i][X] > width2) || + (vertices[i][Y] < nheight2) || + (vertices[i][Y] > height2)) { + return; // this is a bad poly + } + } + } + + if (smooth) { + for (int i = 0; i < vertexCount; i++) { + vertices[i][X] *= SUBXRES; + vertices[i][Y] *= SUBYRES; + } + firstModY = -1; + } + + // find top vertex (y is zero at top, higher downwards) + int topi = 0; + float ymin = vertices[0][Y]; + float ymax = vertices[0][Y]; // fry 031001 + for (int i = 1; i < vertexCount; i++) { + if (vertices[i][Y] < ymin) { + ymin = vertices[i][Y]; + topi = i; + } + if (vertices[i][Y] > ymax) ymax = vertices[i][Y]; + } + + // the last row is an exceptional case, because there won't + // necessarily be 8 rows of subpixel lines that will force + // the final line to render. so instead, the algo keeps track + // of the lastY (in subpixel resolution) that will be rendered + // and that will force a scanline to happen the same as + // every eighth in the other situations + //lastY = -1; // fry 031001 + lastY = (int) (ymax - 0.5f); // global to class bc used by other fxns + + int lefti = topi; // li, index of left vertex + int righti = topi; // ri, index of right vertex + int y = (int) (ymin + 0.5f); // current scan line + int lefty = y - 1; // lower end of left edge + int righty = y - 1; // lower end of right edge + + interpX = true; + + int remaining = vertexCount; + + // scan in y, activating new edges on left & right + // as scan line passes over new vertices + while (remaining > 0) { + // advance left edge? + while ((lefty <= y) && (remaining > 0)) { + remaining--; + // step ccw down left side + int i = (lefti != 0) ? (lefti-1) : (vertexCount-1); + incrementalize_y(vertices[lefti], vertices[i], l, dl, y); + lefty = (int) (vertices[i][Y] + 0.5f); + lefti = i; + } + + // advance right edge? + while ((righty <= y) && (remaining > 0)) { + remaining--; + // step cw down right edge + int i = (righti != vertexCount-1) ? (righti + 1) : 0; + incrementalize_y(vertices[righti], vertices[i], r, dr, y); + righty = (int) (vertices[i][Y] + 0.5f); + righti = i; + } + + // do scanlines till end of l or r edge + while (y < lefty && y < righty) { + // this doesn't work because it's not always set here + //if (remaining == 0) { + //lastY = (lefty < righty) ? lefty-1 : righty-1; + //System.out.println("lastY is " + lastY); + //} + + if ((y >= 0) && (y < height)) { + //try { // hopefully this bug is fixed + if (l[X] <= r[X]) scanline(y, l, r); + else scanline(y, r, l); + //} catch (ArrayIndexOutOfBoundsException e) { + //e.printStackTrace(); + //} + } + y++; + // this increment probably needs to be different + // UV and RGB shouldn't be incremented until line is emitted + increment(l, dl); + increment(r, dr); + } + } + //if (smooth) { + //System.out.println("y/lasty/lastmody = " + y + " " + lastY + " " + lastModY); + //} + } + + + public void unexpand() { + if (smooth) { + for (int i = 0; i < vertexCount; i++) { + vertices[i][X] /= SUBXRES; + vertices[i][Y] /= SUBYRES; + } + } + } + + + private void scanline(int y, float l[], float r[]) { + //System.out.println("scanline " + y); + for (int i = 0; i < vertexCount; i++) { // should be moved later + sp[i] = 0; sdp[i] = 0; + } + + // this rounding doesn't seem to be relevant with smooth + int lx = (int) (l[X] + 0.49999f); // ceil(l[X]-.5); + if (lx < 0) lx = 0; + int rx = (int) (r[X] - 0.5f); + if (rx > width1) rx = width1; + + if (lx > rx) return; + + if (smooth) { + int mody = MODYRES(y); + + aaleft[mody] = lx; + aaright[mody] = rx; + + if (firstModY == -1) { + firstModY = mody; + aaleftmin = lx; aaleftmax = lx; + aarightmin = rx; aarightmax = rx; + + } else { + if (aaleftmin > aaleft[mody]) aaleftmin = aaleft[mody]; + if (aaleftmax < aaleft[mody]) aaleftmax = aaleft[mody]; + if (aarightmin > aaright[mody]) aarightmin = aaright[mody]; + if (aarightmax < aaright[mody]) aarightmax = aaright[mody]; + } + + lastModY = mody; // moved up here (before the return) 031001 + // not the eighth (or lastY) line, so not scanning this time + if ((mody != SUBYRES1) && (y != lastY)) return; + //lastModY = mody; // eeK! this was missing + //return; + + //if (y == lastY) { + //System.out.println("y is lasty"); + //} + //lastModY = mody; + aaleftfull = aaleftmax/SUBXRES + 1; + aarightfull = aarightmin/SUBXRES - 1; + } + + // this is the setup, based on lx + incrementalize_x(l, r, sp, sdp, lx); + + // scan in x, generating pixels + // using parent.width to get actual pixel index + // rather than scaled by smooth factor + int offset = smooth ? parent.width * (y / SUBYRES) : parent.width*y; + + int truelx = 0, truerx = 0; + if (smooth) { + truelx = lx / SUBXRES; + truerx = (rx + SUBXRES1) / SUBXRES; + + lx = aaleftmin / SUBXRES; + rx = (aarightmax + SUBXRES1) / SUBXRES; + if (lx < 0) lx = 0; + if (rx > parent.width1) rx = parent.width1; + } + + interpX = false; + int tr, tg, tb, ta; + + for (int x = lx; x <= rx; x++) { + // added == because things on same plane weren't replacing each other + // makes for strangeness in 3D, but totally necessary for 2D + //if (noDepthTest || (sp[Z] <= zbuffer[offset+x])) { + if (true) { + + // map texture based on U, V coords in sp[U] and sp[V] + if (interpUV) { + int tu = (int)sp[U]; + int tv = (int)sp[V]; + + if (tu > twidth1) tu = twidth1; + if (tv > theight1) tv = theight1; + if (tu < 0) tu = 0; + if (tv < 0) tv = 0; + + int txy = tv*twidth + tu; + + if (smooth || texture_smooth) { + //if (FRY) System.out.println("sp u v = " + sp[U] + " " + sp[V]); + //System.out.println("sp u v = " + sp[U] + " " + sp[V]); + // tuf1/tvf1 is the amount of coverage for the adjacent + // pixel, which is the decimal percentage. + int tuf1 = (int) (255f * (sp[U] - (float)tu)); + int tvf1 = (int) (255f * (sp[V] - (float)tv)); + + // the closer sp[U or V] is to the decimal being zero + // the more coverage it should get of the original pixel + int tuf = 255 - tuf1; + int tvf = 255 - tvf1; + + // this code sucks! filled with bugs and slow as hell! + int pixel00 = tpixels[txy]; + int pixel01 = (tv < theight1) ? + tpixels[txy + twidth] : tpixels[txy]; + int pixel10 = (tu < twidth1) ? + tpixels[txy + 1] : tpixels[txy]; + int pixel11 = ((tv < theight1) && (tu < twidth1)) ? + tpixels[txy + twidth + 1] : tpixels[txy]; + + int p00, p01, p10, p11; + int px0, px1; //, pxy; + + if (tformat == ALPHA) { + px0 = (pixel00*tuf + pixel10*tuf1) >> 8; + px1 = (pixel01*tuf + pixel11*tuf1) >> 8; + ta = (((px0*tvf + px1*tvf1) >> 8) * + (interpARGB ? ((int) (sp[A]*255)) : a2orig)) >> 8; + + } else if (tformat == ARGB) { + p00 = (pixel00 >> 24) & 0xff; + p01 = (pixel01 >> 24) & 0xff; + p10 = (pixel10 >> 24) & 0xff; + p11 = (pixel11 >> 24) & 0xff; + + px0 = (p00*tuf + p10*tuf1) >> 8; + px1 = (p01*tuf + p11*tuf1) >> 8; + ta = (((px0*tvf + px1*tvf1) >> 8) * + (interpARGB ? ((int) (sp[A]*255)) : a2orig)) >> 8; + + } else { // RGB image, no alpha + ta = interpARGB ? ((int) (sp[A]*255)) : a2orig; + } + + if ((tformat == RGB) || (tformat == ARGB)) { + p00 = (pixel00 >> 16) & 0xff; // red + p01 = (pixel01 >> 16) & 0xff; + p10 = (pixel10 >> 16) & 0xff; + p11 = (pixel11 >> 16) & 0xff; + + px0 = (p00*tuf + p10*tuf1) >> 8; + px1 = (p01*tuf + p11*tuf1) >> 8; + tr = (((px0*tvf + px1*tvf1) >> 8) * + (interpARGB ? ((int) sp[R]*255) : r2)) >> 8; + + + p00 = (pixel00 >> 8) & 0xff; // green + p01 = (pixel01 >> 8) & 0xff; + p10 = (pixel10 >> 8) & 0xff; + p11 = (pixel11 >> 8) & 0xff; + + px0 = (p00*tuf + p10*tuf1) >> 8; + px1 = (p01*tuf + p11*tuf1) >> 8; + tg = (((px0*tvf + px1*tvf1) >> 8) * + (interpARGB ? ((int) sp[G]*255) : g2)) >> 8; + + + p00 = pixel00 & 0xff; // blue + p01 = pixel01 & 0xff; + p10 = pixel10 & 0xff; + p11 = pixel11 & 0xff; + + px0 = (p00*tuf + p10*tuf1) >> 8; + px1 = (p01*tuf + p11*tuf1) >> 8; + tb = (((px0*tvf + px1*tvf1) >> 8) * + (interpARGB ? ((int) sp[B]*255) : b2)) >> 8; + + } else { // alpha image, only use current fill color + if (interpARGB) { + tr = (int) (sp[R] * 255); + tg = (int) (sp[G] * 255); + tb = (int) (sp[B] * 255); + + } else { + tr = r2; + tg = g2; + tb = b2; + } + } + + // get coverage for pixel if smooth + // checks smooth again here because of + // hints[SMOOTH_IMAGES] used up above + int weight = smooth ? coverage(x) : 255; + if (weight != 255) ta = ta*weight >> 8; + + } else { // no smooth, just get the pixels + int tpixel = tpixels[txy]; + + // TODO i doubt splitting these guys really gets us + // all that much speed.. is it worth it? + if (tformat == ALPHA) { + ta = tpixel; + + if (interpARGB) { + tr = (int) sp[R]*255; + tg = (int) sp[G]*255; + tb = (int) sp[B]*255; + if (sp[A] != 1) { + ta = (((int) sp[A]*255) * ta) >> 8; + } + + } else { + tr = r2; + tg = g2; + tb = b2; + ta = (a2orig * ta) >> 8; + } + + } else { // RGB or ARGB + ta = (tformat == RGB) ? 255 : (tpixel >> 24) & 0xff; + + if (interpARGB) { + tr = (((int) sp[R]*255) * ((tpixel >> 16) & 0xff)) >> 8; + tg = (((int) sp[G]*255) * ((tpixel >> 8) & 0xff)) >> 8; + tb = (((int) sp[B]*255) * ((tpixel) & 0xff)) >> 8; + ta = (((int) sp[A]*255) * ta) >> 8; + + } else { + tr = (r2 * ((tpixel >> 16) & 0xff)) >> 8; + tg = (g2 * ((tpixel >> 8) & 0xff)) >> 8; + tb = (b2 * ((tpixel) & 0xff)) >> 8; + ta = (a2orig * ta) >> 8; + } + } + } + + if ((ta == 254) || (ta == 255)) { // if (ta & 0xf8) would be good + // no need to blend + pixels[offset+x] = 0xff000000 | (tr << 16) | (tg << 8) | tb; + //zbuffer[offset+x] = sp[Z]; + + } else { + // blend with pixel on screen + int a1 = 255-ta; + int r1 = (pixels[offset+x] >> 16) & 0xff; + int g1 = (pixels[offset+x] >> 8) & 0xff; + int b1 = (pixels[offset+x]) & 0xff; + + pixels[offset+x] = 0xff000000 | + (((tr*ta + r1*a1) >> 8) << 16) | + ((tg*ta + g1*a1) & 0xff00) | + ((tb*ta + b1*a1) >> 8); + //if (ta > ZBUFFER_MIN_COVERAGE) zbuffer[offset+x] = sp[Z]; + } + + } else { // no image applied + int weight = smooth ? coverage(x) : 255; + + if (interpARGB) { + r2 = (int) (sp[R] * 255); + g2 = (int) (sp[G] * 255); + b2 = (int) (sp[B] * 255); + if (sp[A] != 1) weight = (weight * ((int) (sp[A] * 255))) >> 8; + if (weight == 255) { + rgba = 0xff000000 | (r2 << 16) | (g2 << 8) | b2; + } + } else { + if (a2orig != 255) weight = (weight * a2orig) >> 8; + } + + if (weight == 255) { + // no blend, no aa, just the rgba + pixels[offset+x] = rgba; + //zbuffer[offset+x] = sp[Z]; + + } else { + int r1 = (pixels[offset+x] >> 16) & 0xff; + int g1 = (pixels[offset+x] >> 8) & 0xff; + int b1 = (pixels[offset+x]) & 0xff; + a2 = weight; + + int a1 = 255 - a2; + pixels[offset+x] = (0xff000000 | + ((r1*a1 + r2*a2) >> 8) << 16 | + // use & instead of >> and << below + ((g1*a1 + g2*a2) >> 8) << 8 | + ((b1*a1 + b2*a2) >> 8)); + + //if (a2 > ZBUFFER_MIN_COVERAGE) zbuffer[offset+x] = sp[Z]; + } + } + } + // if smooth enabled, don't increment values + // for the pixel in the stretch out version + // of the scanline used to get smooth edges. + if (!smooth || ((x >= truelx) && (x <= truerx))) { + increment(sp, sdp); + } + } + firstModY = -1; + interpX = true; + } + + + // x is in screen, not huge 8x coordinates + private int coverage(int x) { + if ((x >= aaleftfull) && (x <= aarightfull) && + // important since not all SUBYRES lines may have been covered + (firstModY == 0) && (lastModY == SUBYRES1)) { + return 255; + } + + int pixelLeft = x*SUBXRES; // huh? + int pixelRight = pixelLeft + 8; + + int amt = 0; + for (int i = firstModY; i <= lastModY; i++) { + if ((aaleft[i] > pixelRight) || (aaright[i] < pixelLeft)) { + continue; + } + // does this need a +1 ? + amt += ((aaright[i] < pixelRight ? aaright[i] : pixelRight) - + (aaleft[i] > pixelLeft ? aaleft[i] : pixelLeft)); + } + amt <<= 2; + return (amt == 256) ? 255 : amt; + } + + + private void incrementalize_y(float p1[], float p2[], + float p[], float dp[], int y) { + float delta = p2[Y] - p1[Y]; + if (delta == 0) delta = 1; + float fraction = y + 0.5f - p1[Y]; + + if (interpX) { + dp[X] = (p2[X] - p1[X]) / delta; + p[X] = p1[X] + dp[X] * fraction; + } + if (interpZ) { + dp[Z] = (p2[Z] - p1[Z]) / delta; + p[Z] = p1[Z] + dp[Z] * fraction; + } + + if (interpARGB) { + dp[R] = (p2[R] - p1[R]) / delta; + dp[G] = (p2[G] - p1[G]) / delta; + dp[B] = (p2[B] - p1[B]) / delta; + dp[A] = (p2[A] - p1[A]) / delta; + p[R] = p1[R] + dp[R] * fraction; + p[G] = p1[G] + dp[G] * fraction; + p[B] = p1[B] + dp[B] * fraction; + p[A] = p1[A] + dp[A] * fraction; + } + + if (interpUV) { + dp[U] = (p2[U] - p1[U]) / delta; + dp[V] = (p2[V] - p1[V]) / delta; + + //if (smooth) { + //p[U] = p1[U]; //+ dp[U] * fraction; + //p[V] = p1[V]; //+ dp[V] * fraction; + + //} else { + p[U] = p1[U] + dp[U] * fraction; + p[V] = p1[V] + dp[V] * fraction; + //} + if (FRY) System.out.println("inc y p[U] p[V] = " + p[U] + " " + p[V]); + } + } + + + private void incrementalize_x(float p1[], float p2[], + float p[], float dp[], int x) { + float delta = p2[X] - p1[X]; + if (delta == 0) delta = 1; + float fraction = x + 0.5f - p1[X]; + if (smooth) { + delta /= SUBXRES; + fraction /= SUBXRES; + } + + if (interpX) { + dp[X] = (p2[X] - p1[X]) / delta; + p[X] = p1[X] + dp[X] * fraction; + } + if (interpZ) { + dp[Z] = (p2[Z] - p1[Z]) / delta; + p[Z] = p1[Z] + dp[Z] * fraction; + } + + if (interpARGB) { + dp[R] = (p2[R] - p1[R]) / delta; + dp[G] = (p2[G] - p1[G]) / delta; + dp[B] = (p2[B] - p1[B]) / delta; + dp[A] = (p2[A] - p1[A]) / delta; + p[R] = p1[R] + dp[R] * fraction; + p[G] = p1[G] + dp[G] * fraction; + p[B] = p1[B] + dp[B] * fraction; + p[A] = p1[A] + dp[A] * fraction; + } + + if (interpUV) { + if (FRY) System.out.println("delta, frac = " + delta + ", " + fraction); + dp[U] = (p2[U] - p1[U]) / delta; + dp[V] = (p2[V] - p1[V]) / delta; + + //if (smooth) { + //p[U] = p1[U]; + // offset for the damage that will be done by the + // 8 consecutive calls to scanline + // agh.. this won't work b/c not always 8 calls before render + // maybe lastModY - firstModY + 1 instead? + if (FRY) System.out.println("before inc x p[V] = " + p[V] + " " + p1[V] + " " + p2[V]); + //p[V] = p1[V] - SUBXRES1 * fraction; + + //} else { + p[U] = p1[U] + dp[U] * fraction; + p[V] = p1[V] + dp[V] * fraction; + //} + } + } + + + private void increment(float p[], float dp[]) { + if (interpX) p[X] += dp[X]; + if (interpZ) p[Z] += dp[Z]; + + if (interpARGB) { + p[R] += dp[R]; + p[G] += dp[G]; + p[B] += dp[B]; + p[A] += dp[A]; + } + + if (interpUV) { + if (FRY) System.out.println("increment() " + p[V] + " " + dp[V]); + p[U] += dp[U]; + p[V] += dp[V]; + } + } +} diff --git a/core/PShape.java b/core/PShape.java new file mode 100644 index 000000000..0046b975f --- /dev/null +++ b/core/PShape.java @@ -0,0 +1,289 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2006 Ben Fry and Casey Reas + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + + +// take a look at the obj loader to see how this fits with things + +// PShape.line() PShape.ellipse()? +// PShape s = beginShape() +// line() +// endShape(s) + +public class PShape { + + int kind; + PMatrix matrix; + + int[] opcode; + int opcodeCount; + // need to reorder vertex fields to make a VERTEX_SHORT_COUNT + // that puts all the non-rendering fields into later indices + float[][] data; // second param is the VERTEX_FIELD_COUNT + // should this be called vertices (consistent with PGraphics internals) + // or does that hurt flexibility? + + int childCount; + PShape[] children; + + // POINTS, LINES, xLINE_STRIP, xLINE_LOOP + // TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN + // QUADS, QUAD_STRIP + // xPOLYGON + static final int PATH = 1; // POLYGON, LINE_LOOP, LINE_STRIP + static final int GROUP = 2; + + // how to handle rectmode/ellipsemode? + // are they bitshifted into the constant? + // CORNER, CORNERS, CENTER, (CENTER_RADIUS?) + static final int RECT = 3; // could just be QUAD, but would be x1/y1/x2/y2 + static final int ELLIPSE = 4; + + static final int VERTEX = 7; + static final int CURVE = 5; + static final int BEZIER = 6; + + + // fill and stroke functions will need a pointer to the parent + // PGraphics object.. may need some kind of createShape() fxn + // or maybe the values are stored until draw() is called? + + // attaching images is very tricky.. it's a different type of data + + // material parameters will be thrown out, + // except those currently supported (kinds of lights) + + // setAxis -> .x and .y to move x and y coords of origin + public float x; + public float y; + + // pivot point for transformations + public float px; + public float py; + + + public PShape() { + } + + + public PShape(float x, float y) { + this.x = x; + this.y = y; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + /** + * Called by the following (the shape() command adds the g) + * PShape s = loadShapes("blah.svg"); + * shape(s); + */ + public void draw(PGraphics g) { + boolean flat = g instanceof PGraphics3D; + + if (matrix != null) { + g.pushMatrix(); + if (flat) { + g.applyMatrix(matrix.m00, matrix.m01, matrix.m02, + matrix.m10, matrix.m11, matrix.m12); + } else { + g.applyMatrix(matrix.m00, matrix.m01, matrix.m02, matrix.m03, + matrix.m10, matrix.m11, matrix.m12, matrix.m13, + matrix.m20, matrix.m21, matrix.m22, matrix.m23, + matrix.m30, matrix.m31, matrix.m32, matrix.m33); + } + } + + // if g subclasses PGraphics2, ignore all lighting stuff and z coords + // otherwise if PGraphics3, need to call diffuse() etc + + // unfortunately, also a problem with no way to encode stroke/fill + // being enabled/disabled.. this quickly gets into just having opcodes + // for the entire api, to deal with things like textures and images + + switch (kind) { + case PATH: + for (int i = 0; i < opcodeCount; i++) { + switch (opcode[i]) { + case VERTEX: + break; + } + } + break; + + case GROUP: + break; + + case RECT: + break; + } + + if (matrix != null) { + g.popMatrix(); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + // can't be 'add' because that suggests additive geometry + public void addChild(PShape who) { + } + + + public PShape createGroup() { + PShape group = new PShape(); + group.kind = GROUP; + addChild(group); + return group; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + // translate, rotate, scale, apply (no push/pop) + // these each call matrix.translate, etc + // if matrix is null when one is called, + // it is created and set to identity + + + public void translate(float tx, float ty) { + translate(tx, ty, 0); + } + + public void translate(float tx, float ty, float tz) { + checkMatrix(); + matrix.translate(tx, ty, 0); + } + + + // + + + public void rotateX(float angle) { + rotate(angle, 1, 0, 0); + } + + public void rotateY(float angle) { + rotate(angle, 0, 1, 0); + } + + public void rotateZ(float angle) { + rotate(angle, 0, 0, 1); + } + + public void rotate(float angle) { + rotateZ(angle); + } + + public void rotate(float angle, float v0, float v1, float v2) { + checkMatrix(); + matrix.rotate(angle, v0, v1, v2); + } + + + // + + + public void scale(float s) { + scale(s, s, s); + } + + public void scale(float sx, float sy) { + scale(sx, sy, 1); + } + + public void scale(float x, float y, float z) { + checkMatrix(); + matrix.scale(x, y, z); + } + + + // + + + public void applyMatrix(float n00, float n01, float n02, + float n10, float n11, float n12) { + checkMatrix(); + matrix.apply(n00, n01, n02, 0, + n10, n11, n12, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + checkMatrix(); + matrix.apply(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + } + + + // + + + protected void checkMatrix() { + if (matrix == null) { + matrix = new PMatrix(); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + /** + * Center the shape based on its bounding box. Can't assume + * that the bounding box is 0, 0, width, height. Common case will be + * opening a letter size document in Illustrator, and drawing something + * in the middle, then reading it in as an svg file. + * This will also need to flip the y axis (scale(1, -1)) in cases + * like Adobe Illustrator where the coordinates start at the bottom. + */ + public void center() { + } + + + /** + * Set the pivot point for all transformations. + */ + public void pivot(float x, float y) { + px = x; + py = y; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + +} \ No newline at end of file diff --git a/core/PTriangle.java b/core/PTriangle.java new file mode 100644 index 000000000..7142af5b6 --- /dev/null +++ b/core/PTriangle.java @@ -0,0 +1,3827 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-06 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + */ + +package processing.core; + +/** + * Handles rendering of single (tesselated) triangles in 3D. + *

    + * Written by sami www.sumea.com + */ +public class PTriangle implements PConstants +{ + static final int R_GOURAUD = 0x1; + static final int R_TEXTURE8 = 0x2; + static final int R_TEXTURE24 = 0x4; + static final int R_TEXTURE32 = 0x8; + static final int R_ALPHA = 0x10; + + private int[] m_pixels; + private int[] m_texture; + private int[] m_stencil; + private float[] m_zbuffer; + + private int SCREEN_WIDTH; + private int SCREEN_HEIGHT; + //private int SCREEN_WIDTH1; + //private int SCREEN_HEIGHT1; + + private int TEX_WIDTH; + private int TEX_HEIGHT; + private float F_TEX_WIDTH; + private float F_TEX_HEIGHT; + + public boolean INTERPOLATE_UV; + public boolean INTERPOLATE_RGB; + public boolean INTERPOLATE_ALPHA; + + // the power of 2 that tells how many pixels to interpolate + // for between exactly computed texture coordinates + private static final int DEFAULT_INTERP_POWER = 3; + private static int TEX_INTERP_POWER = DEFAULT_INTERP_POWER; + + // Vertex coordinates + private float[] x_array; + private float[] y_array; + private float[] z_array; + + private float[] camX; + private float[] camY; + private float[] camZ; + + // U,V coordinates + private float[] u_array; + private float[] v_array; + + // Vertex Intensity + private float[] r_array; + private float[] g_array; + private float[] b_array; + private float[] a_array; + + // vertex offsets + private int o0; + private int o1; + private int o2; + + /* rgb & a */ + private float r0; + private float r1; + private float r2; + private float g0; + private float g1; + private float g2; + private float b0; + private float b1; + private float b2; + private float a0; + private float a1; + private float a2; + + /* accurate texture uv coordinates */ + private float u0; + private float u1; + private float u2; + private float v0; + private float v1; + private float v2; + + /* deltas */ + //private float dx0; + //private float dx1; + private float dx2; + private float dy0; + private float dy1; + private float dy2; + private float dz0; + //private float dz1; + private float dz2; + + /* texture deltas */ + private float du0; + //private float du1; + private float du2; + private float dv0; + //private float dv1; + private float dv2; + + /* rgba deltas */ + private float dr0; + //private float dr1; + private float dr2; + private float dg0; + //private float dg1; + private float dg2; + private float db0; + //private float db1; + private float db2; + private float da0; + //private float da1; + private float da2; + + /* */ + private float uleft; + private float vleft; + private float uleftadd; + private float vleftadd; + + /* polyedge positions & adds */ + private float xleft; + private float xrght; + private float xadd1; + private float xadd2; + private float zleft; + private float zleftadd; + + /* rgba positions & adds */ + private float rleft; + private float gleft; + private float bleft; + private float aleft; + private float rleftadd; + private float gleftadd; + private float bleftadd; + private float aleftadd; + + /* other somewhat useful variables :) */ + private float dta; + //private float dta2; + private float temp; + private float width; + + /* integer poly UV adds */ + private int iuadd; + private int ivadd; + private int iradd; + private int igadd; + private int ibadd; + private int iaadd; + private float izadd; + + /* fill color */ + private int m_fill; + + /* draw flags */ + public int m_drawFlags; + + /* current poly number */ + private int m_index; + + /** */ + private PGraphics3D parent; + + private boolean noDepthTest; + + /** */ + private boolean m_culling; + + /** */ + private boolean m_singleRight; + + /** */ + private boolean m_bilinear; + + + //Vectors needed in accurate texture code + //We store them as class members to avoid too much code duplication + private float ax,ay,az; + private float bx,by,bz; + private float cx,cy,cz; + private float nearPlaneWidth; + private float nearPlaneHeight; + private float nearPlaneDepth; + private float xmult; + private float ymult; + private float newax,newbx,newcx; //optimization vars...not pretty, but save a couple mults per pixel + private boolean firstSegment; //are we currently drawing the first piece of the triangle, or have we already done so? + + + + + public PTriangle(PGraphics3D g) { + //SCREEN_WIDTH = g.width; + //SCREEN_HEIGHT = g.height; + //SCREEN_WIDTH1 = SCREEN_WIDTH-1; + //SCREEN_HEIGHT1 = SCREEN_HEIGHT-1; + + //m_pixels = g.pixels; + //m_stencil = g.stencil; + //m_zbuffer = g.zbuffer; + + x_array = new float[3]; + y_array = new float[3]; + z_array = new float[3]; + u_array = new float[3]; + v_array = new float[3]; + r_array = new float[3]; + g_array = new float[3]; + b_array = new float[3]; + a_array = new float[3]; + + camX = new float[3]; + camY = new float[3]; + camZ = new float[3]; + + this.parent = g; + reset(); + } + + /** + * Resets polygon attributes + */ + public void reset() { + // reset these in case PGraphics was resized + + SCREEN_WIDTH = parent.width; + SCREEN_HEIGHT = parent.height; + //SCREEN_WIDTH1 = SCREEN_WIDTH-1; + //SCREEN_HEIGHT1 = SCREEN_HEIGHT-1; + + m_pixels = parent.pixels; + m_stencil = parent.stencil; + m_zbuffer = parent.zbuffer; + + noDepthTest = parent.hints[DISABLE_DEPTH_TEST]; + + // other things to reset + + INTERPOLATE_UV = false; + INTERPOLATE_RGB = false; + INTERPOLATE_ALPHA = false; + //m_tImage = null; + m_texture = null; + m_drawFlags = 0; + } + + /** + * Sets backface culling on/off + */ + public void setCulling(boolean tf) { + m_culling = tf; + } + + + /** + * Sets vertex coordinates for the triangle + */ + public void setVertices(float x0, float y0, float z0, + float x1, float y1, float z1, + float x2, float y2, float z2) { + x_array[0] = x0; + x_array[1] = x1; + x_array[2] = x2; + + y_array[0] = y0; + y_array[1] = y1; + y_array[2] = y2; + + z_array[0] = z0; + z_array[1] = z1; + z_array[2] = z2; + } + + /** + * Pass camera-space coordinates for the triangle (needed to render if ENABLE_ACCURATE_TEXTURES is hinted). + */ + public void setCamVertices(float x0, float y0, float z0, + float x1, float y1, float z1, + float x2, float y2, float z2) { + //Generally this will not need to be called manually, currently called if hints[ENABLE_ACCURATE_TEXTURES] + //from PGraphics3D.render_triangles() + + camX[0] = x0; + camX[1] = x1; + camX[2] = x2; + + camY[0] = y0; + camY[1] = y1; + camY[2] = y2; + + camZ[0] = z0; + camZ[1] = z1; + camZ[2] = z2; + } + + /** + * Sets the UV coordinates of the texture + */ + public void setUV(float u0, float v0, + float u1, float v1, + float u2, float v2) { + // sets & scales uv texture coordinates to center of the pixel + u_array[0] = (u0 * F_TEX_WIDTH + 0.5f) * 65536f; + u_array[1] = (u1 * F_TEX_WIDTH + 0.5f) * 65536f; + u_array[2] = (u2 * F_TEX_WIDTH + 0.5f) * 65536f; + v_array[0] = (v0 * F_TEX_HEIGHT + 0.5f) * 65536f; + v_array[1] = (v1 * F_TEX_HEIGHT + 0.5f) * 65536f; + v_array[2] = (v2 * F_TEX_HEIGHT + 0.5f) * 65536f; + } + + /** + * Sets vertex intensities in 0xRRGGBBAA format + */ + public void setIntensities( float r0, float g0, float b0, float a0, + float r1, float g1, float b1, float a1, + float r2, float g2, float b2, float a2) { + // Check if we need alpha or not? + if ((a0 != 1.0f) || (a1 != 1.0f) || (a2 != 1.0f)) { + INTERPOLATE_ALPHA = true; + a_array[0] = (a0 * 253f + 1.0f) * 65536f; + a_array[1] = (a1 * 253f + 1.0f) * 65536f; + a_array[2] = (a2 * 253f + 1.0f) * 65536f; + m_drawFlags|=R_ALPHA; + } else { + INTERPOLATE_ALPHA = false; + m_drawFlags&=~R_ALPHA; + } + + // Check if we need to interpolate the intensity values + if ((r0 != r1) || (r1 != r2)) { + INTERPOLATE_RGB = true; + m_drawFlags|=R_GOURAUD; + } else if ((g0 != g1) || (g1 != g2)) { + INTERPOLATE_RGB = true; + m_drawFlags|=R_GOURAUD; + } else if ((b0 != b1) || (b1 != b2)) { + INTERPOLATE_RGB = true; + m_drawFlags|=R_GOURAUD; + } else { + //m_fill = parent.filli; + m_drawFlags&=~R_GOURAUD; + } + + // push values to arrays.. some extra scaling is added + // to prevent possible color "overflood" due to rounding errors + r_array[0] = (r0 * 253f + 1.0f) * 65536f; + r_array[1] = (r1 * 253f + 1.0f) * 65536f; + r_array[2] = (r2 * 253f + 1.0f) * 65536f; + + g_array[0] = (g0 * 253f + 1.0f) * 65536f; + g_array[1] = (g1 * 253f + 1.0f) * 65536f; + g_array[2] = (g2 * 253f + 1.0f) * 65536f; + + b_array[0] = (b0 * 253f + 1.0f) * 65536f; + b_array[1] = (b1 * 253f + 1.0f) * 65536f; + b_array[2] = (b2 * 253f + 1.0f) * 65536f; + + // for plain triangles + m_fill = ((int)(255*r0) << 16) | ((int)(255*g0) << 8) | (int)(255*b0); + } + + + /** + * Sets texture image used for the polygon + */ + public void setTexture(PImage image) { + //m_tImage = image; + m_texture = image.pixels; + TEX_WIDTH = image.width; + TEX_HEIGHT = image.height; + F_TEX_WIDTH = TEX_WIDTH-1; + F_TEX_HEIGHT = TEX_HEIGHT-1; + INTERPOLATE_UV = true; + + if (image.format == ARGB) { + m_drawFlags|=R_TEXTURE32; + } else if (image.format == RGB) { + m_drawFlags|=R_TEXTURE24; + } else if (image.format == ALPHA) { + m_drawFlags|=R_TEXTURE8; + } + + //if (parent.hints[SMOOTH_IMAGES]) { + /* + if (parent.smooth) { + m_bilinear = true; + } else { + m_bilinear = false; + } + */ + m_bilinear = true; + } + + /** + * + */ + public void setUV(float[] u, float[] v) { + if (m_bilinear) { + // sets & scales uv texture coordinates to edges of pixels + u_array[0] = (u[0] * F_TEX_WIDTH) * 65500f; + u_array[1] = (u[1] * F_TEX_WIDTH) * 65500f; + u_array[2] = (u[2] * F_TEX_WIDTH) * 65500f; + v_array[0] = (v[0] * F_TEX_HEIGHT) * 65500f; + v_array[1] = (v[1] * F_TEX_HEIGHT) * 65500f; + v_array[2] = (v[2] * F_TEX_HEIGHT) * 65500f; + } else { + // sets & scales uv texture coordinates to center of the pixel + u_array[0] = (u[0] * TEX_WIDTH) * 65500f; + u_array[1] = (u[1] * TEX_WIDTH) * 65500f; + u_array[2] = (u[2] * TEX_WIDTH) * 65500f; + v_array[0] = (v[0] * TEX_HEIGHT) * 65500f; + v_array[1] = (v[1] * TEX_HEIGHT) * 65500f; + v_array[2] = (v[2] * TEX_HEIGHT) * 65500f; + } + } + + public void setIndex(int index) { + m_index = index; + } + + /** + * Renders the polygon + */ + public void render() { + // removed. done in PGraphics [rocha] + // increase polygon offset + //m_index = (m_index + 1) & 0xFFFFFFF; + + // draw the polygon + draw(); + + // removed. replaced by external antialiasing [rocha] + // smooth edges? + //if (parent.smooth ) + //{ + // drawline_blender(x_array[0], y_array[0], x_array[1], y_array[1]); + // drawline_blender(x_array[1], y_array[1], x_array[2], y_array[2]); + // drawline_blender(x_array[2], y_array[2], x_array[0], y_array[0]); + //} + } + + private void draw() { + // y-coordinates + float x0; + float x1; + float x2; + + // + float z0; + float z1; + float z2; + + // + float y0 = y_array[0]; + float y1 = y_array[1]; + float y2 = y_array[2]; + + // For accurate texture interpolation, need to mark whether + // we've already pre-calculated for the triangle + firstSegment = true; + + // do backface culling? + if (m_culling) { + x0 = x_array[0]; + if ((x_array[2]-x0)*(y1-y0) < (x_array[1]-x0)*(y2-y0)) + return; + } + + /* get vertex order from top -> down */ + if (y0y1) { + if (y2 SCREEN_HEIGHT) { + return; + } else if (yi0 < 0) { + yi0 = 0; + } + + y2 = y_array[o2]; + int yi2 = (int) (y2 + PIXEL_CENTER); + if (yi2 < 0) { + return; + } else if (yi2 > SCREEN_HEIGHT) { + yi2 = SCREEN_HEIGHT; + } + + // Does the poly actually cross a scanline? + if (yi2 > yi0) { + x0 = x_array[o0]; + x1 = x_array[o1]; + x2 = x_array[o2]; + + // get mid Y and clip it + y1 = y_array[o1]; + int yi1 = (int) (y1 + PIXEL_CENTER); + if (yi1 < 0) + yi1 = 0; + if (yi1 > SCREEN_HEIGHT) + yi1 = SCREEN_HEIGHT; + + // calculate deltas etc. + dx2 = x2 - x0; + dy0 = y1 - y0; + dy2 = y2 - y0; + xadd2 = dx2 / dy2; // xadd for "single" edge + temp = dy0 / dy2; + width = temp * dx2 + x0 - x1; + + // calculate alpha blend interpolation + if (INTERPOLATE_ALPHA) { + a0 = a_array[o0]; + a1 = a_array[o1]; + a2 = a_array[o2]; + da0 = a1-a0; + da2 = a2-a0; + iaadd = (int) ((temp * da2 - da0) / width); // alpha add + } + + // calculate intensity interpolation + if (INTERPOLATE_RGB) { + r0 = r_array[o0]; + r1 = r_array[o1]; + r2 = r_array[o2]; + + g0 = g_array[o0]; + g1 = g_array[o1]; + g2 = g_array[o2]; + + b0 = b_array[o0]; + b1 = b_array[o1]; + b2 = b_array[o2]; + + dr0 = r1-r0; + dg0 = g1-g0; + db0 = b1-b0; + + dr2 = r2-r0; + dg2 = g2-g0; + db2 = b2-b0; + + iradd = (int) ((temp * dr2 - dr0) / width); // r add + igadd = (int) ((temp * dg2 - dg0) / width); // g add + ibadd = (int) ((temp * db2 - db0) / width); // b add + } + + // calculate UV interpolation + if (INTERPOLATE_UV) { + u0 = u_array[o0]; + u1 = u_array[o1]; + u2 = u_array[o2]; + v0 = v_array[o0]; + v1 = v_array[o1]; + v2 = v_array[o2]; + du0 = u1-u0; + dv0 = v1-v0; + du2 = u2-u0; + dv2 = v2-v0; + iuadd = (int) ((temp * du2 - du0) / width); // u add + ivadd = (int) ((temp * dv2 - dv0) / width); // v add + } + + z0 = z_array[o0]; + z1 = z_array[o1]; + z2 = z_array[o2]; + dz0 = z1-z0; + dz2 = z2-z0; + izadd = (temp * dz2 - dz0) / width; + + // draw the upper poly segment if it's visible + if (yi1 > yi0) { + dta = (yi0 + PIXEL_CENTER) - y0; + xadd1 = (x1 - x0) / dy0; + + // we can determine which side is "single" side by comparing left/right edge adds + if (xadd2 > xadd1) { + xleft = x0 + dta * xadd1; + xrght = x0 + dta * xadd2; + zleftadd = dz0 / dy0; + zleft = dta*zleftadd+z0; + + // + if (INTERPOLATE_UV) { + uleftadd = du0 / dy0; + vleftadd = dv0 / dy0; + uleft = dta*uleftadd+u0; + vleft = dta*vleftadd+v0; + } + + // + if (INTERPOLATE_RGB) { + rleftadd = dr0 / dy0; + gleftadd = dg0 / dy0; + bleftadd = db0 / dy0; + rleft = dta*rleftadd+r0; + gleft = dta*gleftadd+g0; + bleft = dta*bleftadd+b0; + } + + // + if (INTERPOLATE_ALPHA) { + aleftadd = da0 / dy0; + aleft = dta*aleftadd+a0; + + if (m_drawFlags == R_ALPHA) { + drawsegment_plain_alpha(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_ALPHA)) { + drawsegment_gouraud_alpha(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_TEXTURE8 + R_ALPHA)) { + drawsegment_texture8_alpha(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_TEXTURE24 + R_ALPHA)) { + drawsegment_texture24_alpha(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_TEXTURE32 + R_ALPHA)) { + drawsegment_texture32_alpha(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE8 + R_ALPHA)) { + drawsegment_gouraud_texture8_alpha(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE24 + R_ALPHA)) { + drawsegment_gouraud_texture24_alpha(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE32 + R_ALPHA)) { + drawsegment_gouraud_texture32_alpha(xadd1,xadd2, yi0,yi1); + } + } else { + if (m_drawFlags == 0) { + drawsegment_plain(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == R_GOURAUD) { + drawsegment_gouraud(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == R_TEXTURE8) { + drawsegment_texture8(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == R_TEXTURE24) { + drawsegment_texture24(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == R_TEXTURE32) { + drawsegment_texture32(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE8)) { + drawsegment_gouraud_texture8(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE24)) { + drawsegment_gouraud_texture24(xadd1,xadd2, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE32)) { + drawsegment_gouraud_texture32(xadd1,xadd2, yi0,yi1); + } + } + m_singleRight = true; + } else { + xleft = x0 + dta * xadd2; + xrght = x0 + dta * xadd1; + zleftadd = dz2 / dy2; + zleft = dta*zleftadd+z0; + // + if (INTERPOLATE_UV) { + uleftadd = du2 / dy2; + vleftadd = dv2 / dy2; + uleft = dta*uleftadd+u0; + vleft = dta*vleftadd+v0; + } + + // + if (INTERPOLATE_RGB) { + rleftadd = dr2 / dy2; + gleftadd = dg2 / dy2; + bleftadd = db2 / dy2; + rleft = dta*rleftadd+r0; + gleft = dta*gleftadd+g0; + bleft = dta*bleftadd+b0; + } + + + if (INTERPOLATE_ALPHA) { + aleftadd = da2 / dy2; + aleft = dta*aleftadd+a0; + + if (m_drawFlags == R_ALPHA) { + drawsegment_plain_alpha(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_ALPHA)) { + drawsegment_gouraud_alpha(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_TEXTURE8 + R_ALPHA)) { + drawsegment_texture8_alpha(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_TEXTURE24 + R_ALPHA)) { + drawsegment_texture24_alpha(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_TEXTURE32 + R_ALPHA)) { + drawsegment_texture32_alpha(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE8 + R_ALPHA)) { + drawsegment_gouraud_texture8_alpha(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE24 + R_ALPHA)) { + drawsegment_gouraud_texture24_alpha(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE32 + R_ALPHA)) { + drawsegment_gouraud_texture32_alpha(xadd2, xadd1, yi0,yi1); + } + } else { + if (m_drawFlags == 0) { + drawsegment_plain(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == R_GOURAUD) { + drawsegment_gouraud(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == R_TEXTURE8) { + drawsegment_texture8(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == R_TEXTURE24) { + drawsegment_texture24(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == R_TEXTURE32) { + drawsegment_texture32(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE8)) { + drawsegment_gouraud_texture8(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE24)) { + drawsegment_gouraud_texture24(xadd2, xadd1, yi0,yi1); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE32)) { + drawsegment_gouraud_texture32(xadd2, xadd1, yi0,yi1); + } + } + m_singleRight = false; + } + + // if bottom segment height is zero, return + if (yi2 == yi1) + return; + + // calculate xadd 1 + dy1 = y2 - y1; + xadd1 = (x2 - x1) / dy1; + } else { + // top seg height was zero, calculate & clip single edge X + dy1 = y2 - y1; + xadd1 = (x2 - x1) / dy1; + + // which edge is left? + if (xadd2 < xadd1) { + xrght = ((yi1 + PIXEL_CENTER) - y0) * xadd2 + x0; + m_singleRight = true; + } else { + dta = (yi1 + PIXEL_CENTER) - y0; + xleft = dta * xadd2 + x0; + zleftadd = dz2 / dy2; + zleft = dta * zleftadd + z0; + + if (INTERPOLATE_UV) { + uleftadd = du2 / dy2; + vleftadd = dv2 / dy2; + uleft = dta * uleftadd + u0; + vleft = dta * vleftadd + v0; + } + + if (INTERPOLATE_RGB) { + rleftadd = dr2 / dy2; + gleftadd = dg2 / dy2; + bleftadd = db2 / dy2; + rleft = dta * rleftadd + r0; + gleft = dta * gleftadd + g0; + bleft = dta * bleftadd + b0; + } + + // + if (INTERPOLATE_ALPHA) { + aleftadd = da2 / dy2; + aleft = dta * aleftadd + a0; + } + m_singleRight = false; + } + } + + // draw the lower segment + if (m_singleRight) { + dta = (yi1 + PIXEL_CENTER) - y1; + xleft = dta * xadd1 + x1; + zleftadd = (z2 - z1) / dy1; + zleft = dta * zleftadd + z1; + + if (INTERPOLATE_UV) { + uleftadd = (u2 - u1) / dy1; + vleftadd = (v2 - v1) / dy1; + uleft = dta * uleftadd + u1; + vleft = dta * vleftadd + v1; + } + + if (INTERPOLATE_RGB) { + rleftadd = (r2 - r1) / dy1; + gleftadd = (g2 - g1) / dy1; + bleftadd = (b2 - b1) / dy1; + rleft = dta * rleftadd + r1; + gleft = dta * gleftadd + g1; + bleft = dta * bleftadd + b1; + } + + if (INTERPOLATE_ALPHA) { + aleftadd = (a2 - a1) / dy1; + aleft = dta * aleftadd + a1; + + if (m_drawFlags == R_ALPHA) { + drawsegment_plain_alpha(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_ALPHA)) { + drawsegment_gouraud_alpha(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_TEXTURE8 + R_ALPHA)) { + drawsegment_texture8_alpha(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_TEXTURE24 + R_ALPHA)) { + drawsegment_texture24_alpha(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_TEXTURE32 + R_ALPHA)) { + drawsegment_texture32_alpha(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE8 + R_ALPHA)) { + drawsegment_gouraud_texture8_alpha(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE24 + R_ALPHA)) { + drawsegment_gouraud_texture24_alpha(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE32 + R_ALPHA)) { + drawsegment_gouraud_texture32_alpha(xadd1, xadd2, yi1,yi2); + } + } else { + if (m_drawFlags == 0) { + drawsegment_plain(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == R_GOURAUD) { + drawsegment_gouraud(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == R_TEXTURE8) { + drawsegment_texture8(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == R_TEXTURE24) { + drawsegment_texture24(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == R_TEXTURE32) { + drawsegment_texture32(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE8)) { + drawsegment_gouraud_texture8(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE24)) { + drawsegment_gouraud_texture24(xadd1, xadd2, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE32)) { + drawsegment_gouraud_texture32(xadd1, xadd2, yi1,yi2); + } + } + } else { + xrght = ((yi1 + PIXEL_CENTER)- y1) * xadd1 + x1; + + if (INTERPOLATE_ALPHA) { + if (m_drawFlags == R_ALPHA) { + drawsegment_plain_alpha(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_ALPHA)) { + drawsegment_gouraud_alpha(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_TEXTURE8 + R_ALPHA)) { + drawsegment_texture8_alpha(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_TEXTURE24 + R_ALPHA)) { + drawsegment_texture24_alpha(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_TEXTURE32 + R_ALPHA)) { + drawsegment_texture32_alpha(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE8 + R_ALPHA)) { + drawsegment_gouraud_texture8_alpha(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE24 + R_ALPHA)) { + drawsegment_gouraud_texture24_alpha(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE32 + R_ALPHA)) { + drawsegment_gouraud_texture32_alpha(xadd2, xadd1, yi1,yi2); + } + } else { + if (m_drawFlags == 0) { + drawsegment_plain(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == R_GOURAUD) { + drawsegment_gouraud(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == R_TEXTURE8) { + drawsegment_texture8(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == R_TEXTURE24) { + drawsegment_texture24(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == R_TEXTURE32) { + drawsegment_texture32(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE8)) { + drawsegment_gouraud_texture8(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE24)) { + drawsegment_gouraud_texture24(xadd2, xadd1, yi1,yi2); + } else if (m_drawFlags == (R_GOURAUD + R_TEXTURE32)) { + drawsegment_gouraud_texture32(xadd2, xadd1, yi1,yi2); + } + } + } + } + } + + + /* + Accurate texturing code by ewjordan@gmail.com, April 14, 2007 + The getColorFromTexture() function should be inlined and optimized so that most of the heavy lifting + happens outside the per-pixel loop. The unoptimized generic algorithm looks like this (unless noted, + all of these variables are vectors, so the actual code will look messier): + + p = camera space vector where u == 0, v == 0; + m = vector from p to location where u == TEX_WIDTH; + n = vector from p to location where v == TEX_HEIGHT; + + A = p cross n; + B = m cross p; + C = n cross m; + A *= texture.width; + B *= texture.height; + + for (scanlines in triangle){ + float a = S * A; + float b = S * B; + float c = S * C; + for (pixels in scanline){ + int u = a/c; + int v = b/c; + color = texture[v * texture.width + u]; + a += A.x; + b += B.x; + c += C.x; + } + } + + We don't use this exact algorithm here, however, because of the extra overhead from the divides. + Instead we compute the exact u and v (labelled iu and iv in the code) at the start of each scanline + and we perform a linear interpolation for every linearInterpLength = 1 << TEX_INTERP_POWER pixels. + This means that we only perform the true calculation once in a while, and the rest of the time + the algorithm functions exactly as in the fast inaccurate case, at least in theory. In practice, + even if we set linearInterpLength very high we still incur some speed penalty due to the preprocessing + that must take place per-scanline. A similar method could be applied per scanline to avoid this, but + it would only be worthwhile in the case that we never compute more than one exact calculation per + scanline. If we want something like this, however, it would be best to create another mode of calculation + called "constant-z" interpolation, which could be used for things like floors and ceilings where the + distance from the camera plane never changes over the course of a scanline. We could also add the + vertical analogue for drawing vertical walls. In any case, these are not critical as the current + algorithm runs fairly well, perhaps ~10% slower than the default perspective-less one. + + */ + + /** + * Solve for camera space coordinates of critical texture points and set up per-triangle variables for accurate texturing + */ + private boolean precomputeAccurateTexturing(){ + //Sets all class variables relevant to accurate texture computation + //Should be called once per triangle - checks firstSegment to see if we've already called + + float myFact = 65500.0f; //rescale u/v_array values when inverting matrix and performing other calcs + float myFact2 = 65500.0f; + + //Matrix inversion to find affine transform between (u,v,(1)) -> (x,y,z) + + //OPTIMIZE: There should be a way to avoid the inversion here, which is + //quite expensive (~150 mults). Also it might crap out due to loss of precision depending + //on the scaling of the u/v_arrays. Nothing clever currently happens if the inversion + //fails, since this means the transformation is degenerate - we just pass false back to + //the caller and let it handle the situation. [There is no good solution to this + //case from within this function, since the way the calculation proceeds presumes a non- + //degenerate transformation matrix between camera space and uv space] + + //Obvious optimization: if the vertices are actually at the appropriate texture coordinates + //(e.g. (0,0), (TEX_WIDTH,0), and (0,TEX_HEIGHT)) then we can immediately return the + //right solution without the inversion. This is fairly common, so could speed up + //many cases of drawing. [not implemented] + + //Furthermore, we could cache the p,resultT0,result0T vectors in the triangle's + //basis, since we could then do a look-up and generate the resulting coordinates very simply. + //This would include the above optimization as a special case - we could pre-populate the + //cache with special cases like that and dynamically add more. The idea here is + //that most people simply paste textures onto triangles and move the triangles from + //frame to frame, so any per-triangle-per-frame code is likely wasted effort. + //[not implemented] + + //Note: o0, o1, and o2 vary depending on view angle to triangle, but p, n, and m should not depend on ordering differences + + if(firstSegment){ + + PMatrix myMatrix = new PMatrix( u_array[o0]/myFact, v_array[o0]/myFact2, 1, 0, + u_array[o1]/myFact, v_array[o1]/myFact2, 1, 0, + u_array[o2]/myFact, v_array[o2]/myFact2, 1, 0, + 0, 0, 0, 1); + myMatrix = myMatrix.invert(); //A 3x3 inversion would be more efficient here, given that the fourth r/c are unity + if (myMatrix == null) {return false;} //if the matrix inversion had trouble, let the caller know + float m00, m01, m02, m10, m11, m12, m20, m21, m22; + m00 = myMatrix.m00*camX[o0]+myMatrix.m01*camX[o1]+myMatrix.m02*camX[o2]; + m01 = myMatrix.m10*camX[o0]+myMatrix.m11*camX[o1]+myMatrix.m12*camX[o2]; + m02 = myMatrix.m20*camX[o0]+myMatrix.m21*camX[o1]+myMatrix.m22*camX[o2]; + m10 = myMatrix.m00*camY[o0]+myMatrix.m01*camY[o1]+myMatrix.m02*camY[o2]; + m11 = myMatrix.m10*camY[o0]+myMatrix.m11*camY[o1]+myMatrix.m12*camY[o2]; + m12 = myMatrix.m20*camY[o0]+myMatrix.m21*camY[o1]+myMatrix.m22*camY[o2]; + m20 = -(myMatrix.m00*camZ[o0]+myMatrix.m01*camZ[o1]+myMatrix.m02*camZ[o2]); + m21 = -(myMatrix.m10*camZ[o0]+myMatrix.m11*camZ[o1]+myMatrix.m12*camZ[o2]); + m22 = -(myMatrix.m20*camZ[o0]+myMatrix.m21*camZ[o1]+myMatrix.m22*camZ[o2]); + + float px = m02; + float py = m12; + float pz = m22; + float resultT0x = m00*TEX_WIDTH+m02; //Bugfix: possibly we should use F_TEX_WIDTH/HEIGHT instead? Seems to read off end of array in that case, though... + float resultT0y = m10*TEX_WIDTH+m12; + float resultT0z = m20*TEX_WIDTH+m22; + float result0Tx = m01*TEX_HEIGHT+m02; + float result0Ty = m11*TEX_HEIGHT+m12; + float result0Tz = m21*TEX_HEIGHT+m22; + float mx = resultT0x-m02; + float my = resultT0y-m12; + float mz = resultT0z-m22; + float nx = result0Tx-m02; + float ny = result0Ty-m12; + float nz = result0Tz-m22; + + //avec = p x n + ax = (py*nz-pz*ny)*TEX_WIDTH; //F_TEX_WIDTH/HEIGHT? + ay = (pz*nx-px*nz)*TEX_WIDTH; + az = (px*ny-py*nx)*TEX_WIDTH; + //bvec = m x p + bx = (my*pz-mz*py)*TEX_HEIGHT; + by = (mz*px-mx*pz)*TEX_HEIGHT; + bz = (mx*py-my*px)*TEX_HEIGHT; + //cvec = n x m + cx = ny*mz-nz*my; + cy = nz*mx-nx*mz; + cz = nx*my-ny*mx; + } + + nearPlaneWidth = parent.rightScreen-parent.leftScreen; + nearPlaneHeight = parent.topScreen-parent.bottomScreen; + nearPlaneDepth = parent.nearPlane; + xmult = nearPlaneWidth / SCREEN_WIDTH; //one pixel width in nearPlane coordinates + ymult = nearPlaneHeight / SCREEN_HEIGHT; + newax = ax*xmult;//Extra scalings to map screen plane units to pixel units + newbx = bx*xmult; + newcx = cx*xmult; + return true; + } + + /** + * Set the power of two used for linear interpolation of texture coordinates. + * A true texture coordinate is computed every 2^pwr pixels along a scanline. + */ + static public void setInterpPower(int pwr){ + //Currently must be invoked from P5 as PTriangle.setInterpPower(...) + TEX_INTERP_POWER = pwr; + } + + + + /** + * Plain color + */ + private void drawsegment_plain + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int f = m_fill; + int p = m_index; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + + float xdiff = (xstart + PIXEL_CENTER) - xleft; + float iz = izadd * xdiff + zleft; + xstart+=ytop; + xend+=ytop; + + for ( ; xstart < xend; xstart++ ) { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + m_zbuffer[xstart] = iz; + m_pixels[xstart] = f; + m_stencil[xstart] = p; + } + iz+=izadd; + } + + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + zleft+=zleftadd; + } + } + + /** + * Plain color, interpolated alpha + */ + private void drawsegment_plain_alpha + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + + int pr = m_fill & 0xFF0000; + int pg = m_fill & 0xFF00; + int pb = m_fill & 0xFF; + + int p = m_index; + float iaf = iaadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + + float xdiff = (xstart + PIXEL_CENTER) - xleft; + float iz = izadd * xdiff + zleft; + int ia = (int) (iaf * xdiff + aleft); + xstart+=ytop; + xend+=ytop; + + for ( ; xstart < xend; xstart++ ) { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + int alpha = ia >> 16; + int mr0 = m_pixels[xstart]; + int mg0 = mr0 & 0xFF00; + int mb0 = mr0 & 0xFF; + mr0 &= 0xFF0000; + + mr0 = mr0 + (((pr - mr0) * alpha) >> 8); + mg0 = mg0 + (((pg - mg0) * alpha) >> 8); + mb0 = mb0 + (((pb - mb0) * alpha) >> 8); + m_pixels[xstart] = (mr0 & 0xFF0000) | (mg0 & 0xFF00) | (mb0 & 0xFF); + + m_stencil[xstart] = p; + } + iz += izadd; + ia += iaadd; + } + ytop += SCREEN_WIDTH; + xleft += leftadd; + xrght += rghtadd; + zleft += zleftadd; + } + } + + + /** + * RGB gouraud + */ + private void drawsegment_gouraud + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + float irf = iradd; + float igf = igadd; + float ibf = ibadd; + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + + float xdiff = (xstart + PIXEL_CENTER) - xleft; + int ir = (int) (irf * xdiff + rleft); + int ig = (int) (igf * xdiff + gleft); + int ib = (int) (ibf * xdiff + bleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + for ( ; xstart < xend; xstart++ ) { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + m_zbuffer[xstart] = iz; + m_pixels[xstart]=((ir & 0xFF0000) | ((ig >> 8) & 0xFF00) | (ib >> 16)); + m_stencil[xstart] = p; + } + + // + ir+=iradd; + ig+=igadd; + ib+=ibadd; + iz+=izadd; + } + + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + rleft+=rleftadd; + gleft+=gleftadd; + bleft+=bleftadd; + zleft+=zleftadd; + } + } + + + /** + * RGB gouraud + interpolated alpha + */ + private void drawsegment_gouraud_alpha + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float irf = iradd; + float igf = igadd; + float ibf = ibadd; + float iaf = iaadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + float xdiff = (xstart + PIXEL_CENTER) - xleft; + + int ir = (int) (irf * xdiff + rleft); + int ig = (int) (igf * xdiff + gleft); + int ib = (int) (ibf * xdiff + bleft); + int ia = (int) (iaf * xdiff + aleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + for ( ; xstart < xend; xstart++ ) { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + // + int red = (ir & 0xFF0000); + int grn = (ig >> 8) & 0xFF00; + int blu = (ib >> 16); + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + + // blend alpha + int al = ia >> 16; + + // + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) | ((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + m_stencil[xstart] = p; + } + + // + ir+=iradd; + ig+=igadd; + ib+=ibadd; + ia+=iaadd; + iz+=izadd; + } + + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + rleft+=rleftadd; + gleft+=gleftadd; + bleft+=bleftadd; + aleft+=aleftadd; + zleft+=zleftadd; + } + } + + + /** + * 8-bit plain texture + */ + + //THIS IS MESSED UP, NEED TO GRAB ORIGINAL VERSION!!! + private void drawsegment_texture8 + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + + int red = m_fill & 0xFF0000; + int grn = m_fill & 0xFF00; + int blu = m_fill & 0xFF; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + + float xdiff = (xstart + PIXEL_CENTER) - xleft; + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + // try-catch just in case pixel offset it out of range + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + int al0; + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = iu & 0xFFFF; + al0 = m_texture[ofs] & 0xFF; + int al1 = m_texture[ofs + 1] & 0xFF; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int al2 = m_texture[ofs] & 0xFF; + int al3 = m_texture[ofs + 1] & 0xFF; + al0 = al0 + (((al1-al0) * iui) >> 16); + al2 = al2 + (((al3-al2) * iui) >> 16); + al0 = al0 + (((al2-al0) * (iv & 0xFFFF)) >> 16); + } else { + al0 = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)] & 0xFF; + } + + int br = m_pixels[xstart]; + int bg = (br & 0xFF00); + int bb = (br & 0xFF); + br = (br & 0xFF0000); + m_pixels[xstart] = ((br + (((red - br) * al0) >> 8)) & 0xFF0000) | ((bg + (((grn - bg) * al0) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al0) >> 8)) & 0xFF); + m_stencil[xstart] = p; + } + } + catch (Exception e) { + } + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + iz+=izadd; + } + ypixel++;//accurate mode + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + zleft+=zleftadd; + } + } + + + + /** + * 8-bit texutre + alpha + */ + private void drawsegment_texture8_alpha + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float iaf = iaadd; + + int red = m_fill & 0xFF0000; + int grn = m_fill & 0xFF00; + int blu = m_fill & 0xFF; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + + float xdiff = (xstart + PIXEL_CENTER) - xleft; + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ia = (int) (iaf * xdiff + aleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + // try-catch just in case pixel offset it out of range + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + int al0; + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = iu & 0xFFFF; + al0 = m_texture[ofs] & 0xFF; + int al1 = m_texture[ofs + 1] & 0xFF; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int al2 = m_texture[ofs] & 0xFF; + int al3 = m_texture[ofs + 1] & 0xFF; + al0 = al0 + (((al1-al0) * iui) >> 16); + al2 = al2 + (((al3-al2) * iui) >> 16); + al0 = al0 + (((al2-al0) * (iv & 0xFFFF)) >> 16); + } else { + al0 = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)] & 0xFF; + } + al0 = (al0 * (ia >> 16)) >> 8; + + int br = m_pixels[xstart]; + int bg = (br & 0xFF00); + int bb = (br & 0xFF); + br = (br & 0xFF0000); + m_pixels[xstart] = ((br + (((red - br) * al0) >> 8)) & 0xFF0000) | ((bg + (((grn - bg) * al0) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al0) >> 8)) & 0xFF); + m_stencil[xstart] = p; + } + } + catch (Exception e) { + } + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + iz+=izadd; + ia+=iaadd; + } + ypixel++;//accurate mode + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + zleft+=zleftadd; + aleft+=aleftadd; + } + } + + /** + * Plain 24-bit texture + */ + private void drawsegment_texture24 + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + float iuf = iuadd; + float ivf = ivadd; + + int ypixel = ytop/SCREEN_WIDTH;//ACCTEX + int lastRowStart = m_texture.length - TEX_WIDTH - 2;//If we're past this index, we can't shift down a row w/o throwing an exception +// int exCount = 0;//counter for exceptions caught + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; //bring this local since it will be accessed often + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + + //Interpolation length of 16 tends to look good except at a small angle; 8 looks okay then, except for the + //above issue. When viewing close to flat, as high as 32 is often acceptable. Could add dynamic interpolation + //settings based on triangle angle - currently we just pick a value and leave it (by default I have the + //power set at 3, so every 8 pixels a true coordinate is calculated, which seems a decent compromise). + + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion gave us garbage, revert to normal rendering (something is degenerate) + } + } + + + while (ytop < ybottom) {//scanline loop + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0){ xstart = 0; } + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH){ xend = SCREEN_WIDTH; } + float xdiff = (xstart + PIXEL_CENTER) - xleft; + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + float iz = izadd * xdiff + zleft; + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + //off by one (half, I guess) hack, w/o it the first rows are outside the texture - maybe a mistake somewhere? + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az;//OPT - some of this could be brought out of the y-loop since + b = screenx*bx+screeny*by+screenz*bz;//xpixel and ypixel just increment by the same numbers each iteration. + c = screenx*cx+screeny*cy+screenz*cz;//Probably not a big bottleneck, though. + } + + //Figure out whether triangle is going further into the screen or not as we move along scanline + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + + //Set up linear interpolation between calculated texture points + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + //float fdeltaU = 0; float fdeltaV = 0;//vars for floating point interpolating version of algorithm + //float fiu = 0; float fiv = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + + //Bugfix (done): it's a Really Bad Thing to interpolate along a scanline when the triangle goes deeper into the screen, + //because if the angle is severe enough the last control point for interpolation may cross the vanishing + //point. This leads to some pretty nasty artifacts, and ideally we should scan from right to left if the + //triangle is better served that way, or (what we do now) precompute the offset that we'll need so that we end up + //with a control point exactly at the furthest edge of the triangle. + + if (accurateMode&&goingIn){ + //IMPORTANT!!! Results are horrid without this hack! + //If polygon goes into the screen along scan line, we want to match the control point to the furthest point drawn + //since the control points are less meaningful the closer you are to the vanishing point. + //We'll do this by making the first control point lie before the start of the scanline (safe since it's closer to us) + + int rightOffset = (xend-xstart-1)%linearInterpLength; //"off by one" hack...probably means there's a small bug somewhere + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + + //Take step to control point to the left of start pixel + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + + //Now step to right control point + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + + //Get deltas for interpolation + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + //Otherwise the left edge is further, and we pin the first control point to it + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + for ( ; xstart < xend; xstart++ ) {//pixel loop - keep trim, can execute thousands of times per frame + //boolean drawBlack = false; //used to display control points + if(accurateMode){ + /* //Non-interpolating algorithm - slowest version, calculates exact coordinate for each pixel, + //and casts from float->int + float oneoverc = 65536.0f/c; //a bit faster to pre-divide for next two steps + iu = (int)(a*oneoverc); + iv = (int)(b*oneoverc); + a += newax; + b += newbx; + c += newcx; + */ + + //Float while calculating, int while interpolating + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + //drawBlack = true; + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; //ints are used for interpolation, not actual computation + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ //race through using linear interpolation if we're not at a control point + iu += deltaU; + iv += deltaV; + } + interpCounter++; + + /* //Floating point interpolating version - slower than int thanks to casts during interpolation steps + if (interpCounter == 0) { + interpCounter = linearInterpLength; + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; + oldfv = fv; + fu = (a*oneoverc); + fv = (b*oneoverc); + //oldu = u; oldv = v; + fiu = oldfu; + fiv = oldfv; + fdeltaU = (fu-oldfu)/linearInterpLength; + fdeltaV = (fv-oldfv)/linearInterpLength; + } + else{ + fiu += fdeltaU; + fiv += fdeltaV; + } + interpCounter--; + iu = (int)(fiu); iv = (int)(fiv);*/ + + } + + // try-catch just in case pixel offset is out of range + try{ + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + m_zbuffer[xstart] = iz; + if (m_bilinear) { + //We could (should?) add bounds checking on iu and iv here (keep in mind the 16 bit shift!). + //This would also be the place to add looping texture mode (bounds check == clamped). + //For looping/clamped textures, we'd also need to change PGraphics.textureVertex() to remove + //the texture range check there (it constrains normalized texture coordinates from 0->1). + + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = (iu & 0xFFFF) >> 9; + int ivi = (iv & 0xFFFF) >> 9; + + //if(ofs < 0) { ofs += TEX_WIDTH; } + //if(ofs > m_texture.length-2) {ofs -= TEX_WIDTH; } + + // get texture pixels + int pix0 = m_texture[ofs]; + int pix1 = m_texture[ofs + 1]; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; //quick hack to thwart exceptions + int pix2 = m_texture[ofs]; + int pix3 = m_texture[ofs + 1]; + + // red + int red0 = (pix0 & 0xFF0000); + int red2 = (pix2 & 0xFF0000); + int up = red0 + ((((pix1 & 0xFF0000) - red0) * iui) >> 7); + int dn = red2 + ((((pix3 & 0xFF0000) - red2) * iui) >> 7); + int red = up + (((dn-up) * ivi) >> 7); + + // grn + red0 = (pix0 & 0xFF00); + red2 = (pix2 & 0xFF00); + up = red0 + ((((pix1 & 0xFF00) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF00) - red2) * iui) >> 7); + int grn = up + (((dn-up) * ivi) >> 7); + + // blu + red0 = (pix0 & 0xFF); + red2 = (pix2 & 0xFF); + up = red0 + ((((pix1 & 0xFF) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF) - red2) * iui) >> 7); + int blu = up + (((dn-up) * ivi) >> 7); + + m_pixels[xstart] = (red & 0xFF0000) | (grn & 0xFF00) | (blu & 0xFF); + //if (drawBlack){ m_pixels[xstart] = 0; } + + } else{ + m_pixels[xstart] = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)]; + } + m_stencil[xstart] = p; + } + } catch (Exception e) {/*exCount++;*/} + iz+=izadd; + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + } + ypixel++;//accurate mode + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + zleft+=zleftadd; + uleft+=uleftadd; + vleft+=vleftadd; + } +// if (exCount>0) System.out.println(exCount+" exceptions in this segment"); + } + + + /** + * Alpha 24-bit texture + */ + private void drawsegment_texture24_alpha + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float iaf = iaadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + + float xdiff = (xstart + PIXEL_CENTER) - xleft; + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ia = (int) (iaf * xdiff + aleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + // get alpha + int al = ia >> 16; + + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = (iu & 0xFFFF) >> 9; + int ivi = (iv & 0xFFFF) >> 9; + + // get texture pixels + int pix0 = m_texture[ofs]; + int pix1 = m_texture[ofs + 1]; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int pix2 = m_texture[ofs]; + int pix3 = m_texture[ofs + 1]; + + // red + int red0 = (pix0 & 0xFF0000); + int red2 = (pix2 & 0xFF0000); + int up = red0 + ((((pix1 & 0xFF0000) - red0) * iui) >> 7); + int dn = red2 + ((((pix3 & 0xFF0000) - red2) * iui) >> 7); + int red = up + (((dn-up) * ivi) >> 7); + + // grn + red0 = (pix0 & 0xFF00); + red2 = (pix2 & 0xFF00); + up = red0 + ((((pix1 & 0xFF00) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF00) - red2) * iui) >> 7); + int grn = up + (((dn-up) * ivi) >> 7); + + // blu + red0 = (pix0 & 0xFF); + red2 = (pix2 & 0xFF); + up = red0 + ((((pix1 & 0xFF) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF) - red2) * iui) >> 7); + int blu = up + (((dn-up) * ivi) >> 7); + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |( (bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + } else { + int red = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)]; + int grn = red & 0xFF00; + int blu = red & 0xFF; + red&=0xFF0000; + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + } + m_stencil[xstart] = p; + } + } + catch (Exception e) {} + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + ia+=iaadd; + iz+=izadd; + } + ypixel++;//accurate mode + + ytop+=SCREEN_WIDTH; + + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + zleft+=zleftadd; + aleft+=aleftadd; + } + } + + /** + * Plain 32-bit texutre + */ + private void drawsegment_texture32 + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + + float xdiff = (xstart + PIXEL_CENTER) - xleft; + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + // try-catch just in case pixel offset it out of range + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = (iu & 0xFFFF) >> 9; + int ivi = (iv & 0xFFFF) >> 9; + + // get texture pixels + int pix0 = m_texture[ofs]; + int pix1 = m_texture[ofs + 1]; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int pix2 = m_texture[ofs]; + int pix3 = m_texture[ofs + 1]; + + // red + int red0 = (pix0 & 0xFF0000); + int red2 = (pix2 & 0xFF0000); + int up = red0 + ((((pix1 & 0xFF0000) - red0) * iui) >> 7); + int dn = red2 + ((((pix3 & 0xFF0000) - red2) * iui) >> 7); + int red = up + (((dn-up) * ivi) >> 7); + + // grn + red0 = (pix0 & 0xFF00); + red2 = (pix2 & 0xFF00); + up = red0 + ((((pix1 & 0xFF00) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF00) - red2) * iui) >> 7); + int grn = up + (((dn-up) * ivi) >> 7); + + // blu + red0 = (pix0 & 0xFF); + red2 = (pix2 & 0xFF); + up = red0 + ((((pix1 & 0xFF) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF) - red2) * iui) >> 7); + int blu = up + (((dn-up) * ivi) >> 7); + + // alpha + pix0>>>=24; + pix2>>>=24; + up = pix0 + ((((pix1 >>> 24) - pix0) * iui) >> 7); + dn = pix2 + ((((pix3 >>> 24) - pix2) * iui) >> 7); + int al = up + (((dn-up) * ivi) >> 7); + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + } else { + int red = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)]; + int al = red >>> 24; + int grn = red & 0xFF00; + int blu = red & 0xFF; + red&=0xFF0000; + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + } + m_stencil[xstart] = p; + } + } + catch (Exception e) { + } + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + iz+=izadd; + } + ypixel++;//accurate mode + + ytop+=SCREEN_WIDTH; + + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + zleft+=zleftadd; + aleft+=aleftadd; + } + + + } + + /** + * Alpha 32-bit texutre + */ + private void drawsegment_texture32_alpha + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float iaf = iaadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + + float xdiff = (xstart + PIXEL_CENTER) - xleft; + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ia = (int) (iaf * xdiff + aleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + // try-catch just in case pixel offset it out of range + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + // get alpha + int al = ia >> 16; + + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = (iu & 0xFFFF) >> 9; + int ivi = (iv & 0xFFFF) >> 9; + + // get texture pixels + int pix0 = m_texture[ofs]; + int pix1 = m_texture[ofs + 1]; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int pix2 = m_texture[ofs]; + int pix3 = m_texture[ofs + 1]; + + // red + int red0 = (pix0 & 0xFF0000); + int red2 = (pix2 & 0xFF0000); + int up = red0 + ((((pix1 & 0xFF0000) - red0) * iui) >> 7); + int dn = red2 + ((((pix3 & 0xFF0000) - red2) * iui) >> 7); + int red = up + (((dn-up) * ivi) >> 7); + + // grn + red0 = (pix0 & 0xFF00); + red2 = (pix2 & 0xFF00); + up = red0 + ((((pix1 & 0xFF00) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF00) - red2) * iui) >> 7); + int grn = up + (((dn-up) * ivi) >> 7); + + // blu + red0 = (pix0 & 0xFF); + red2 = (pix2 & 0xFF); + up = red0 + ((((pix1 & 0xFF) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF) - red2) * iui) >> 7); + int blu = up + (((dn-up) * ivi) >> 7); + + // alpha + pix0>>>=24; + pix2>>>=24; + up = pix0 + ((((pix1 >>> 24) - pix0) * iui) >> 7); + dn = pix2 + ((((pix3 >>> 24) - pix2) * iui) >> 7); + al = al * (up + (((dn-up) * ivi) >> 7)) >> 8; + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + } else { + int red = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)]; + al = al * (red >>> 24) >> 8; + int grn = red & 0xFF00; + int blu = red & 0xFF; + red&=0xFF0000; + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + } + m_stencil[xstart] = p; + } + } + catch (Exception e) { + } + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + ia+=iaadd; + iz+=izadd; + } + ypixel++;//accurate mode + + ytop+=SCREEN_WIDTH; + + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + zleft+=zleftadd; + aleft+=aleftadd; + } + + + } + + + /** + * Gouraud blended with 8-bit alpha texture + */ + private void drawsegment_gouraud_texture8 + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float irf = iradd; + float igf = igadd; + float ibf = ibadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + float xdiff = (xstart + PIXEL_CENTER) - xleft; + + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ir = (int) (irf * xdiff + rleft); + int ig = (int) (igf * xdiff + gleft); + int ib = (int) (ibf * xdiff + bleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + int al0; + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = iu & 0xFFFF; + al0 = m_texture[ofs] & 0xFF; + int al1 = m_texture[ofs + 1] & 0xFF; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int al2 = m_texture[ofs] & 0xFF; + int al3 = m_texture[ofs + 1] & 0xFF; + al0 = al0 + (((al1-al0) * iui) >> 16); + al2 = al2 + (((al3-al2) * iui) >> 16); + al0 = al0 + (((al2-al0) * (iv & 0xFFFF)) >> 16); + } else { + al0 = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)] & 0xFF; + } + + // get RGB colors + int red = ir & 0xFF0000; + int grn = (ig >> 8) & 0xFF00; + int blu = (ib >> 16); + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + m_pixels[xstart] = ((br + (((red - br) * al0) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al0) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al0) >> 8)) & 0xFF); + + // write stencil + m_stencil[xstart] = p; + } + } + catch (Exception e) { + + } + + // + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + ir+=iradd; + ig+=igadd; + ib+=ibadd; + iz+=izadd; + } + ypixel++;//accurate mode + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + + uleft+=uleftadd; + vleft+=vleftadd; + rleft+=rleftadd; + gleft+=gleftadd; + bleft+=bleftadd; + zleft+=zleftadd; + } + } + + + /** + * Texture multiplied with gouraud + */ + private void drawsegment_gouraud_texture8_alpha + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float irf = iradd; + float igf = igadd; + float ibf = ibadd; + float iaf = iaadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + float xdiff = (xstart + PIXEL_CENTER) - xleft; + + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ir = (int) (irf * xdiff + rleft); + int ig = (int) (igf * xdiff + gleft); + int ib = (int) (ibf * xdiff + bleft); + int ia = (int) (iaf * xdiff + aleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + int al0; + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = iu & 0xFFFF; + al0 = m_texture[ofs] & 0xFF; + int al1 = m_texture[ofs + 1] & 0xFF; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int al2 = m_texture[ofs] & 0xFF; + int al3 = m_texture[ofs + 1] & 0xFF; + al0 = al0 + (((al1-al0) * iui) >> 16); + al2 = al2 + (((al3-al2) * iui) >> 16); + al0 = al0 + (((al2-al0) * (iv & 0xFFFF)) >> 16); + } else { + al0 = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)] & 0xFF; + } + al0 = (al0 * (ia >> 16)) >> 8; + + // get RGB colors + int red = ir & 0xFF0000; + int grn = (ig >> 8) & 0xFF00; + int blu = (ib >> 16); + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + m_pixels[xstart] = ((br + (((red - br) * al0) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al0) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al0) >> 8)) & 0xFF); + + // write stencil + m_stencil[xstart] = p; + } + } + catch (Exception e) { + + } + + // + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + ir+=iradd; + ig+=igadd; + ib+=ibadd; + ia+=iaadd; + iz+=izadd; + } + ypixel++;//accurate mode + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + rleft+=rleftadd; + gleft+=gleftadd; + bleft+=bleftadd; + aleft+=aleftadd; + zleft+=zleftadd; + } + } + + + /** + * Texture multiplied with gouraud + */ + private void drawsegment_gouraud_texture24 + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float irf = iradd; + float igf = igadd; + float ibf = ibadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + float xdiff = (xstart + PIXEL_CENTER) - xleft; + + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ir = (int) (irf * xdiff + rleft); + int ig = (int) (igf * xdiff + gleft); + int ib = (int) (ibf * xdiff + bleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + m_zbuffer[xstart] = iz; + + int red; + int grn; + int blu; + + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = (iu & 0xFFFF) >> 9; + int ivi = (iv & 0xFFFF) >> 9; + + // get texture pixels + int pix0 = m_texture[ofs]; + int pix1 = m_texture[ofs + 1]; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int pix2 = m_texture[ofs]; + int pix3 = m_texture[ofs + 1]; + + // red + int red0 = (pix0 & 0xFF0000); + int red2 = (pix2 & 0xFF0000); + int up = red0 + ((((pix1 & 0xFF0000) - red0) * iui) >> 7); + int dn = red2 + ((((pix3 & 0xFF0000) - red2) * iui) >> 7); + red = up + (((dn-up) * ivi) >> 7); + + // grn + red0 = (pix0 & 0xFF00); + red2 = (pix2 & 0xFF00); + up = red0 + ((((pix1 & 0xFF00) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF00) - red2) * iui) >> 7); + grn = up + (((dn-up) * ivi) >> 7); + + // blu + red0 = (pix0 & 0xFF); + red2 = (pix2 & 0xFF); + up = red0 + ((((pix1 & 0xFF) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF) - red2) * iui) >> 7); + blu = up + (((dn-up) * ivi) >> 7); + } else { + // get texture pixel color + blu = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)]; + red = (blu & 0xFF0000); + grn = (blu & 0xFF00); + blu = blu & 0xFF; + } + + // + int r = (ir >> 16); + int g = (ig >> 16); + int bb2 = (ib >> 16); //oops, namespace collision with accurate texture vector b...sorry [ewjordan] + + // + m_pixels[xstart] = ( ((red * r) & 0xFF000000) | ((grn * g) & 0xFF0000) | (blu * bb2) ) >> 8; + m_stencil[xstart] = p; + } + } + catch (Exception e) { + } + + // + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + ir+=iradd; + ig+=igadd; + ib+=ibadd; + iz+=izadd; + } + ypixel++;//accurate mode + + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + rleft+=rleftadd; + gleft+=gleftadd; + bleft+=bleftadd; + zleft+=zleftadd; + } + } + + + /** + * Gouraud*texture blended with interpolating alpha + */ + private void drawsegment_gouraud_texture24_alpha + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float irf = iradd; + float igf = igadd; + float ibf = ibadd; + float iaf = iaadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + float xdiff = (xstart + PIXEL_CENTER) - xleft; + + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ir = (int) (irf * xdiff + rleft); + int ig = (int) (igf * xdiff + gleft); + int ib = (int) (ibf * xdiff + bleft); + int ia = (int) (iaf * xdiff + aleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + for ( ;xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + // get texture pixel color + try + { + //if (iz < m_zbuffer[xstart]) { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { // [fry 041114] + //m_zbuffer[xstart] = iz; + + // blend + int al = ia >> 16; + + int red; + int grn; + int blu; + + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = (iu & 0xFFFF) >> 9; + int ivi = (iv & 0xFFFF) >> 9; + + // get texture pixels + int pix0 = m_texture[ofs]; + int pix1 = m_texture[ofs + 1]; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int pix2 = m_texture[ofs]; + int pix3 = m_texture[ofs + 1]; + + // red + int red0 = (pix0 & 0xFF0000); + int red2 = (pix2 & 0xFF0000); + int up = red0 + ((((pix1 & 0xFF0000) - red0) * iui) >> 7); + int dn = red2 + ((((pix3 & 0xFF0000) - red2) * iui) >> 7); + red = (up + (((dn-up) * ivi) >> 7)) >> 16; + + // grn + red0 = (pix0 & 0xFF00); + red2 = (pix2 & 0xFF00); + up = red0 + ((((pix1 & 0xFF00) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF00) - red2) * iui) >> 7); + grn = (up + (((dn-up) * ivi) >> 7)) >> 8; + + // blu + red0 = (pix0 & 0xFF); + red2 = (pix2 & 0xFF); + up = red0 + ((((pix1 & 0xFF) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF) - red2) * iui) >> 7); + blu = up + (((dn-up) * ivi) >> 7); + } else { + blu = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)]; + red = (blu & 0xFF0000) >> 16; // 0 - 255 + grn = (blu & 0xFF00) >> 8; // 0 - 255 + blu = (blu & 0xFF); // 0 - 255 + } + + // multiply with gouraud color + red = (red * ir) >>> 8; // 0x00FF???? + grn = (grn * ig) >>> 16; // 0x0000FF?? + blu = (blu * ib) >>> 24; // 0x000000FF + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + + // + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + m_stencil[xstart] = p; + } + } + catch (Exception e) { + } + + // + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + ir+=iradd; + ig+=igadd; + ib+=ibadd; + ia+=iaadd; + iz+=izadd; + } + + ypixel++;//accurate mode + + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + rleft+=rleftadd; + gleft+=gleftadd; + bleft+=bleftadd; + aleft+=aleftadd; + zleft+=zleftadd; + } + } + + + /** + * Gouraud*texture blended with interpolating alpha + */ + private void drawsegment_gouraud_texture32 + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + //int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float irf = iradd; + float igf = igadd; + float ibf = ibadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + float xdiff = (xstart + PIXEL_CENTER) - xleft; + + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ir = (int) (irf * xdiff + rleft); + int ig = (int) (igf * xdiff + gleft); + int ib = (int) (ibf * xdiff + bleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + + for ( ; xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + try + { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { + //m_zbuffer[xstart] = iz; + + int red; + int grn; + int blu; + int al; + + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = (iu & 0xFFFF) >> 9; + int ivi = (iv & 0xFFFF) >> 9; + + // get texture pixels + int pix0 = m_texture[ofs]; + int pix1 = m_texture[ofs + 1]; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int pix2 = m_texture[ofs]; + int pix3 = m_texture[ofs + 1]; + + // red + int red0 = (pix0 & 0xFF0000); + int red2 = (pix2 & 0xFF0000); + int up = red0 + ((((pix1 & 0xFF0000) - red0) * iui) >> 7); + int dn = red2 + ((((pix3 & 0xFF0000) - red2) * iui) >> 7); + red = (up + (((dn-up) * ivi) >> 7)) >> 16; + + // grn + red0 = (pix0 & 0xFF00); + red2 = (pix2 & 0xFF00); + up = red0 + ((((pix1 & 0xFF00) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF00) - red2) * iui) >> 7); + grn = (up + (((dn-up) * ivi) >> 7)) >> 8; + + // blu + red0 = (pix0 & 0xFF); + red2 = (pix2 & 0xFF); + up = red0 + ((((pix1 & 0xFF) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF) - red2) * iui) >> 7); + blu = up + (((dn-up) * ivi) >> 7); + + // alpha + pix0>>>=24; + pix2>>>=24; + up = pix0 + ((((pix1 >>> 24) - pix0) * iui) >> 7); + dn = pix2 + ((((pix3 >>> 24) - pix2) * iui) >> 7); + al = up + (((dn-up) * ivi) >> 7); + } else { + // get texture pixel color + blu = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)]; + al = (blu >>> 24); + red = (blu & 0xFF0000) >> 16; + grn = (blu & 0xFF00) >> 8; + blu = blu & 0xFF; + } + + // multiply with gouraud color + red = (red * ir) >>> 8; // 0x00FF???? + grn = (grn * ig) >>> 16; // 0x0000FF?? + blu = (blu * ib) >>> 24; // 0x000000FF + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + + // + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + } + } + catch (Exception e) { + } + + // + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + ir+=iradd; + ig+=igadd; + ib+=ibadd; + iz+=izadd; + } + ypixel++;//accurate mode + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + rleft+=rleftadd; + gleft+=gleftadd; + bleft+=bleftadd; + zleft+=zleftadd; + } + } + + + /** + * Gouraud*texture blended with interpolating alpha + */ + private void drawsegment_gouraud_texture32_alpha + ( + float leftadd, + float rghtadd, + int ytop, + int ybottom + ) { + //Accurate texture mode added - comments stripped from dupe code, see drawsegment_texture24() for details + int ypixel = ytop; + int lastRowStart = m_texture.length - TEX_WIDTH - 2; + boolean accurateMode = parent.hints[ENABLE_ACCURATE_TEXTURES]; + float screenx = 0; float screeny = 0; float screenz = 0; + float a = 0; float b = 0; float c = 0; + int linearInterpPower = TEX_INTERP_POWER; + int linearInterpLength = 1 << linearInterpPower; + if (accurateMode){ + if(precomputeAccurateTexturing()){ //see if the precomputation goes well, if so finish the setup + newax *= linearInterpLength; + newbx *= linearInterpLength; + newcx *= linearInterpLength; + screenz = nearPlaneDepth; + firstSegment = false; + } else{ + accurateMode = false; //if the matrix inversion screwed up, revert to normal rendering (something is degenerate) + } + } + + ytop*=SCREEN_WIDTH; + ybottom*=SCREEN_WIDTH; + int p = m_index; + + float iuf = iuadd; + float ivf = ivadd; + float irf = iradd; + float igf = igadd; + float ibf = ibadd; + float iaf = iaadd; + + while (ytop < ybottom) { + int xstart = (int) (xleft + PIXEL_CENTER); + if (xstart < 0) + xstart = 0; + + int xpixel = xstart;//accurate mode + + int xend = (int) (xrght + PIXEL_CENTER); + if (xend > SCREEN_WIDTH) + xend = SCREEN_WIDTH; + float xdiff = (xstart + PIXEL_CENTER) - xleft; + + int iu = (int) (iuf * xdiff + uleft); + int iv = (int) (ivf * xdiff + vleft); + int ir = (int) (irf * xdiff + rleft); + int ig = (int) (igf * xdiff + gleft); + int ib = (int) (ibf * xdiff + bleft); + int ia = (int) (iaf * xdiff + aleft); + float iz = izadd * xdiff + zleft; + + xstart+=ytop; + xend+=ytop; + if (accurateMode){ + screenx = xmult*(xpixel+.5f-(SCREEN_WIDTH/2.0f)); + screeny = ymult*(ypixel+.5f-(SCREEN_HEIGHT/2.0f)); + a = screenx*ax+screeny*ay+screenz*az; + b = screenx*bx+screeny*by+screenz*bz; + c = screenx*cx+screeny*cy+screenz*cz; + } + boolean goingIn = ( (newcx > 0) == (c > 0) )?false:true; + int interpCounter = 0; + int deltaU = 0; int deltaV = 0; + float fu = 0; float fv = 0; + float oldfu = 0; float oldfv = 0; + + if (accurateMode&&goingIn){ + int rightOffset = (xend-xstart-1)%linearInterpLength; + int leftOffset = linearInterpLength-rightOffset; + float rightOffset2 = rightOffset / ((float)linearInterpLength); + float leftOffset2 = leftOffset / ((float)linearInterpLength); + interpCounter = leftOffset; + float ao = a-leftOffset2*newax; + float bo = b-leftOffset2*newbx; + float co = c-leftOffset2*newcx; + float oneoverc = 65536.0f/co; + oldfu = (ao*oneoverc); oldfv = (bo*oneoverc); + a += rightOffset2*newax; + b += rightOffset2*newbx; + c += rightOffset2*newcx; + oneoverc = 65536.0f/c; + fu = a*oneoverc; fv = b*oneoverc; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + iu = ( (int)oldfu )+(leftOffset-1)*deltaU; iv = ( (int)oldfv )+(leftOffset-1)*deltaV; //another "off-by-one" hack + } else{ + float preoneoverc = 65536.0f/c; + fu = (a*preoneoverc); + fv = (b*preoneoverc); + } + + for ( ;xstart < xend; xstart++ ) { + if(accurateMode){ + if (interpCounter == linearInterpLength) interpCounter = 0; + if (interpCounter == 0){ + a += newax; + b += newbx; + c += newcx; + float oneoverc = 65536.0f/c; + oldfu = fu; oldfv = fv; + fu = (a*oneoverc); fv = (b*oneoverc); + iu = (int)oldfu; iv = (int)oldfv; + deltaU = ((int)(fu - oldfu)) >> linearInterpPower; + deltaV = ((int)(fv - oldfv)) >> linearInterpPower; + } else{ + iu += deltaU; + iv += deltaV; + } + interpCounter++; + } + + // get texture pixel color + try + { + //if (iz < m_zbuffer[xstart]) { + if (noDepthTest || (iz <= m_zbuffer[xstart])) { // [fry 041114] + //m_zbuffer[xstart] = iz; + + // blend + int al = ia >> 16; + + int red; + int grn; + int blu; + + if (m_bilinear) { + int ofs = (iv >> 16) * TEX_WIDTH + (iu >> 16); + int iui = (iu & 0xFFFF) >> 9; + int ivi = (iv & 0xFFFF) >> 9; + + // get texture pixels + int pix0 = m_texture[ofs]; + int pix1 = m_texture[ofs + 1]; + if (ofs < lastRowStart) ofs+=TEX_WIDTH; + int pix2 = m_texture[ofs]; + int pix3 = m_texture[ofs + 1]; + + // red + int red0 = (pix0 & 0xFF0000); + int red2 = (pix2 & 0xFF0000); + int up = red0 + ((((pix1 & 0xFF0000) - red0) * iui) >> 7); + int dn = red2 + ((((pix3 & 0xFF0000) - red2) * iui) >> 7); + red = (up + (((dn-up) * ivi) >> 7)) >> 16; + + // grn + red0 = (pix0 & 0xFF00); + red2 = (pix2 & 0xFF00); + up = red0 + ((((pix1 & 0xFF00) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF00) - red2) * iui) >> 7); + grn = (up + (((dn-up) * ivi) >> 7)) >> 8; + + // blu + red0 = (pix0 & 0xFF); + red2 = (pix2 & 0xFF); + up = red0 + ((((pix1 & 0xFF) - red0) * iui) >> 7); + dn = red2 + ((((pix3 & 0xFF) - red2) * iui) >> 7); + blu = up + (((dn-up) * ivi) >> 7); + + // alpha + pix0>>>=24; + pix2>>>=24; + up = pix0 + ((((pix1 >>> 24) - pix0) * iui) >> 7); + dn = pix2 + ((((pix3 >>> 24) - pix2) * iui) >> 7); + al = al * (up + (((dn-up) * ivi) >> 7)) >> 8; + } else { + blu = m_texture[(iv >> 16) * TEX_WIDTH + (iu >> 16)]; + al = al * (blu >>> 24) >> 8; + red = (blu & 0xFF0000) >> 16; // 0 - 255 + grn = (blu & 0xFF00) >> 8; // 0 - 255 + blu = (blu & 0xFF); // 0 - 255 + } + + // multiply with gouraud color + red = (red * ir) >>> 8; // 0x00FF???? + grn = (grn * ig) >>> 16; // 0x0000FF?? + blu = (blu * ib) >>> 24; // 0x000000FF + + // get buffer pixels + int bb = m_pixels[xstart]; + int br = (bb & 0xFF0000); // 0x00FF0000 + int bg = (bb & 0xFF00); // 0x0000FF00 + bb = (bb & 0xFF); // 0x000000FF + + // + m_pixels[xstart] = ((br + (((red - br) * al) >> 8)) & 0xFF0000) |((bg + (((grn - bg) * al) >> 8)) & 0xFF00) | ((bb + (((blu - bb) * al) >> 8)) & 0xFF); + m_stencil[xstart] = p; + } + } + catch (Exception e) { + } + + // + xpixel++;//accurate mode + if (!accurateMode){ + iu+=iuadd; + iv+=ivadd; + } + ir+=iradd; + ig+=igadd; + ib+=ibadd; + ia+=iaadd; + iz+=izadd; + } + ypixel++;//accurate mode + ytop+=SCREEN_WIDTH; + xleft+=leftadd; + xrght+=rghtadd; + uleft+=uleftadd; + vleft+=vleftadd; + rleft+=rleftadd; + gleft+=gleftadd; + bleft+=bleftadd; + aleft+=aleftadd; + zleft+=zleftadd; + } + } +}