001/*
002 * FrameBufferPanel. The MIT License.
003 * Copyright (c) 2022 rlkraft@pnw.edu
004 * See LICENSE for details.
005*/
006
007package renderer.framebuffer;
008
009import  renderer.framebuffer.FrameBuffer;
010
011import java.awt.Color;
012import java.awt.Graphics;
013import java.awt.Graphics2D;
014import java.awt.Dimension;
015import java.awt.Image;
016import java.awt.image.MemoryImageSource;
017import javax.swing.JPanel;
018
019/**
020   This class is an interface between our renderer and the Java GUI system.
021   This class allows our rendering code to be used as the primary renderer
022   for Java programs. That is, this class allows us to write Java programs
023   that use our renderer instead of the renderer built into Java's GUI
024   library. Of course, our renderer will be much slower than the one built
025   into Java (which uses the computer's GPU).
026<p>
027   A {@code FrameBufferPanel} "is a" {@link javax.swing.JPanel}. A
028   {@code FrameBufferPanel} "has a" {@link FrameBuffer}. And a
029   {@link FrameBuffer} "has a" array of pixel data. The pixel data in a
030   {@link FrameBuffer} is put there by calling our rendering algorithms.
031<p>
032   Each instance of {@code FrameBufferPanel} has a reference to a
033   {@link FrameBuffer} object and the {@link FrameBuffer} object
034   determines the (preferred) dimensions of the {@code FrameBufferPanel}.
035<p>
036   A {@code FrameBufferPanel} should be instantiated by an application that
037   uses our renderer. The application should initialize a {@link renderer.scene.Scene}
038   object with appropriate models and geometry. The application should then
039   render the {@link renderer.scene.Scene} into the {@link FrameBuffer} object
040   contained in the {@code FrameBufferPanel}.
041<p>
042   This class is meant to be instantiated as a sub-panel of a
043   {@link javax.swing.JFrame}. The {@link javax.swing.JFrame} may or may
044   not implement event listeners. If the {@link javax.swing.JFrame} does
045   implement event listeners, then the event listeners can make our
046   renderer interactive.
047<p>
048   When a GUI event happens, any implemented event listener should update
049   this {@code FrameBufferPanel} by modifying a {@link renderer.scene.Scene}
050   object appropriately and then having our renderer render the
051   {@link renderer.scene.Scene} object into this object's {@link FrameBuffer}.
052   When the renderer is done updating the {@link FrameBuffer}, the event
053   listener should call this object's {@link java.awt.Component#repaint}
054   method, which will lead to the calling of this object's {@link #paintComponent}
055   method, which will pass the {@link FrameBuffer}'s pixel data to an
056   {@link java.awt.Image} that will be drawn on the {@link java.awt.Graphics}
057   context of the {@code FrameBufferPanel} (which is a {@link JPanel}).
058   This will display the {@link java.awt.Image}, which holds the
059   {@link FrameBuffer}'s contents, in the {@link JPanel} within a
060   {@link javax.swing.JFrame}'s window.
061<p>
062   This panel may be resizeable. When this panel resizes, its
063   {@link FrameBuffer} object will also need to resize. But
064   {@link FrameBuffer} objects cannot be resized. So each time this
065   panel resizes, a new {@link FrameBuffer} object needs to be created.
066   The {@link java.awt.event.ComponentListener#componentResized} event
067   handler from the {@link java.awt.event.ComponentListener} interface
068   should instantiate a new {@link FrameBuffer} object with the
069   appropriate dimensions and then call this object's {@link #setFrameBuffer}
070   method and pass it a reference to the new {@link FrameBuffer} object.
071*/
072@SuppressWarnings("serial")
073public final class FrameBufferPanel extends JPanel
074{
075   private FrameBuffer fb;
076   private MemoryImageSource source;
077
078   /**
079      @param fbWidth   width for the initial {@link FrameBuffer} used by this {@link JPanel}
080      @param fbHeight  height for the initial {@link FrameBuffer} used by this {@link JPanel}
081   */
082   public FrameBufferPanel(final int fbWidth, final int fbHeight)
083   {
084      // Create the initial FrameBuffer for the FrameBufferPanel.
085      this.setFrameBuffer( new FrameBuffer(fbWidth, fbHeight) );
086   }
087
088
089   /**
090      @param fbWidth   width for the initial {@link FrameBuffer} used by this {@link JPanel}
091      @param fbHeight  height for the initial {@link FrameBuffer} used by this {@link JPanel}
092      @param bgColor   background {@link Color} for the initial {@link FrameBuffer} used by this {@link JPanel}
093   */
094   public FrameBufferPanel(final int fbWidth, final int fbHeight, final Color bgColor)
095   {
096      // Create the initial FrameBuffer for the FrameBufferPanel.
097      this.setFrameBuffer( new FrameBuffer(fbWidth, fbHeight, bgColor) );
098   }
099
100
101   @Override
102   public Dimension getPreferredSize()
103   {
104      return new Dimension(fb.width, fb.height);
105   }
106
107
108   @Override
109   protected void paintComponent(Graphics g)
110   {
111      super.paintComponent(g);
112
113      final Graphics2D g2 = (Graphics2D)g.create();
114      final Image img = this.createImage(this.source);
115      g2.drawImage(img, 0, 0, this);
116      g2.dispose();
117   }
118
119
120   /**
121      Accessor method for the {@link FrameBuffer} currently being used as
122      the source for the {@link java.awt.Image} painted on this {@link JPanel}.
123
124      @return a reference to the {@link FrameBuffer} owned by this {@link JPanel}
125   */
126   public FrameBuffer getFrameBuffer()
127   {
128      return fb;
129   }
130
131
132   /**
133      Change the {@link FrameBuffer} being used as the source for
134      the {@link java.awt.Image} painted on this {@link JPanel}.
135   <p>
136      This will usually be in response to a call to the
137      componentResized() event handler.
138
139      @param fb  new {@link FrameBuffer} object for this {@link JPanel}
140   */
141   public void setFrameBuffer(final FrameBuffer fb)
142   {
143      this.fb = fb;
144
145      // Use the framebuffer as the source for an Image.
146      this.source = new MemoryImageSource(fb.width, fb.height,
147                                          fb.pixel_buffer,
148                                          0, fb.width);
149   }
150}