diff --git a/java/2-8-Transformation/BarExercise.java b/java/2-8-Transformation/BarExercise.java new file mode 100644 index 0000000000000000000000000000000000000000..89b1e8024d9fb6247515df64afc52d379639fa4b --- /dev/null +++ b/java/2-8-Transformation/BarExercise.java @@ -0,0 +1,130 @@ +/* +* CS 349 Java Code Examples +* +* BarExercise Demo of multiple transformation exercise. +* +*/ +import javax.swing.JFrame; +import javax.swing.JComponent; +import javax.swing.JButton; +import java.awt.*; +import java.awt.geom.*; +import java.util.ArrayList; +import javax.vecmath.*; +import java.lang.Math.*; +import java.util.Random; +import java.awt.event.*; +import java.awt.BorderLayout; +import java.awt.FlowLayout; + +// create the window and run the demo +public class BarExercise { + + public static void main(String[] args) { + // create the window + Canvas canvas = new Canvas(); + JFrame f = new JFrame("BarExercise"); // jframe is the app window + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setSize(500, 500); // window size + f.setContentPane(canvas); // add canvas to jframe + f.setBackground(Color.WHITE); + f.setVisible(true); // show the window + } +} + +class Canvas extends JComponent { + + // the house shape (model position is centred at top left corner) + private Polygon shape = new Polygon(new int[] { -50, 50, 50, 0, -50}, + new int[] { 75, 75, -25, -75, -25}, 5); + + int step = 0; + + Canvas() { + + // only mouse clicked events + addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent me) { + step ++; + repaint(); + } + }); + } + + // custom graphics drawing + public void paintComponent(Graphics g) { + super.paintComponent(g); // JPanel paint + Graphics2D g2 = (Graphics2D)g; + + // make a centred grid + int gridSize = 250; + g2.translate((this.getWidth() - gridSize) / 2, (this.getHeight() - gridSize) / 2); + drawGrid(g2, gridSize, gridSize, gridSize / 10); + + // draw the original shape in "model" coordinates + g2.setColor(Color.BLACK); + drawBar(g2, 50, 100, 150, 100); + + // save the current transform matrix + AffineTransform M = g2.getTransform(); + + // the shape will get transformed into "world" coordinates + if (true) { + g2.translate(50, 100); + g2.rotate(Math.toRadians(30)); + g2.translate(-50, -100); + g2.setColor(Color.BLUE.darker()); + drawBar(g2, 50, 100, 150, 100); + + // demo the steps + } else { + g2.setColor(Color.BLUE.darker()); + switch (step % 4) { + + case 1: + g2.translate(-50, -100); + drawBar(g2, 50, 100, 150, 100); + g2.setTransform(M); + break; + case 2: + g2.rotate(Math.toRadians(30)); + g2.translate(-50, -100); + drawBar(g2, 50, 100, 150, 100); + g2.setTransform(M); + break; + case 3: + g2.translate(50, 100); + g2.rotate(Math.toRadians(30)); + g2.translate(-50, -100); + drawBar(g2, 50, 100, 150, 100); + g2.setTransform(M); + break; + } + } + } + + private void drawBar(Graphics2D g2, int x1, int y1, int x2, int y2) { + int d = 12; + g2.setStroke(new BasicStroke(4)); + g2.drawLine(x1, y1, x2, y2); + g2.fillOval(x1 - d/2, y1 - d/2, d, d); + g2.fillOval(x2 - d/2, y2 - d/2, d, d); + } + + + private void drawGrid(Graphics2D g2, int w, int h, int s) { + + // Draw grid + g2.setStroke(new BasicStroke(1)); + g2.setColor(Color.GRAY.brighter()); + // horizontal lines + for(int i = 0; i <= h; i += s) { + g2.drawLine(0, i, w, i); + } + // vertical lines + for(int i = 0; i <= w; i += s) { + g2.drawLine(i, 0, i, h); + } + } + +} diff --git a/java/2-8-Transformation/CompositionOrder.java b/java/2-8-Transformation/CompositionOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..e4a2e88cb3eac89d584b0174361d24ebb5a95879 --- /dev/null +++ b/java/2-8-Transformation/CompositionOrder.java @@ -0,0 +1,181 @@ +/* +* CS 349 Java Code Examples +* +* CompositionOrder Demo of different concatenation orders of matrix transforms. +* Click the window to change the order. +* +*/ +import javax.swing.JFrame; +import javax.swing.JComponent; +import javax.swing.JButton; +import java.awt.*; +import java.awt.geom.*; +import java.util.ArrayList; +import javax.vecmath.*; +import java.lang.Math.*; +import java.util.Random; +import java.awt.event.*; +import java.awt.BorderLayout; +import java.awt.FlowLayout; + +// create the window and run the demo +public class CompositionOrder { + + public static void main(String[] args) { + // create the window + Canvas canvas = new Canvas(); + JFrame f = new JFrame("CompositionOrder"); // jframe is the app window + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setSize(500, 500); // window size + f.setContentPane(canvas); // add canvas to jframe + f.setBackground(Color.WHITE); + f.setVisible(true); // show the window + } +} + +class Canvas extends JComponent { + + // rotate + double theta = 30; + // translate + double tx = 100; + double ty = 0; + // scale + double sx = 2; + double sy = 1.2; + + // the house shape (model position is centred at top left corner) + private Polygon shape = new Polygon(new int[] { -50, 50, 50, 0, -50}, + new int[] { 75, 75, -25, -75, -25}, 5); + + // a larger font for displaying the concatenation order + private Font font = new Font("SansSerif", Font.PLAIN, 30); + + // the concatenation order + private int order = 0; + + Canvas() { + + // only mouse clicked events + addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent me) { + order = (order + 1) % 6; + repaint(); + } + }); + + System.out.println("click to change transformation composition order"); + } + + // custom graphics drawing + public void paintComponent(Graphics g) { + super.paintComponent(g); // JPanel paint + Graphics2D g2 = (Graphics2D)g; + + // make a centred grid + int gridSize = 250; + g2.translate((this.getWidth() - gridSize) / 2, (this.getHeight() - gridSize) / 2); + drawGrid(g2, gridSize, gridSize, gridSize / 10); + + // save the current transform matrix + AffineTransform M = g2.getTransform(); + + // draw the original shape in "model" coordinates + g2.setColor(Color.BLACK); + g2.setStroke(new BasicStroke(3)); + g2.drawPolygon(shape.xpoints, shape.ypoints, shape.npoints); + // mark 0, 0 too + g2.setStroke(new BasicStroke(1)); + g2.drawOval(-5, -5, 10, 10); + + // create transformation matrices + AffineTransform R = + AffineTransform.getRotateInstance(Math.toRadians(theta)); + AffineTransform T = + AffineTransform.getTranslateInstance(tx, ty); + AffineTransform S = + AffineTransform.getScaleInstance(sx, sy); + + // concatenate the matrices in 1 of 6 orders + String s = "p'="; + switch (order) + { + case 0: + s += "TRS"; + g2.transform(T); + g2.transform(R); + g2.transform(S); + break; + + case 1: + s += "TSR"; + g2.transform(T); + g2.transform(S); + g2.transform(R); + break; + + case 2: + s += "RST"; + g2.transform(R); + g2.transform(S); + g2.transform(T); + break; + + case 3: + s += "RTS"; + g2.transform(R); + g2.transform(T); + g2.transform(S); + break; + + case 4: + s += "SRT"; + g2.transform(S); + g2.transform(R); + g2.transform(T); + break; + + case 5: + s += "STR"; + g2.transform(S); + g2.transform(T); + g2.transform(R); + break; + } + s += "p"; + + // the shape will get transformed into "world" coordinates + g2.setColor(Color.RED); + g2.setStroke(new BasicStroke(3)); + g2.drawPolygon(shape.xpoints, shape.ypoints, shape.npoints); + // mark 0, 0 too + g2.setStroke(new BasicStroke(1)); + g2.drawOval(-5, -5, 10, 10); + + // reset to transform before we did the T, R, and S + // so we can draw the text + g2.setTransform(M); + + // display the order text + g2.setColor(Color.BLACK); + g2.setFont(font); + g2.drawString(s, 0, gridSize + 50); + } + + + private void drawGrid(Graphics2D g2, int w, int h, int s) { + + // Draw grid + g2.setStroke(new BasicStroke(1)); + g2.setColor(Color.GRAY.brighter()); + // horizontal lines + for(int i = 0; i <= h; i += s) { + g2.drawLine(0, i, w, i); + } + // vertical lines + for(int i = 0; i <= w; i += s) { + g2.drawLine(i, 0, i, h); + } + } + +} diff --git a/java/2-8-Transformation/SceneGraph.java b/java/2-8-Transformation/SceneGraph.java new file mode 100644 index 0000000000000000000000000000000000000000..96b744337823f54a6fa0feed810ea184d5f63465 --- /dev/null +++ b/java/2-8-Transformation/SceneGraph.java @@ -0,0 +1,135 @@ +/* +* CS 349 Java Code Examples +* +* SceneGraph Demonstrate simple scene graph that draws a house. +* +*/ + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.geom.*; + +public class SceneGraph extends JPanel { + + public static void main(String[] args) { + // create the window + JFrame f = new JFrame("SceneGraph"); // jframe is the app window + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setSize(400, 400); // window size + f.setContentPane(new SceneGraph()); // add canvas to jframe + f.setVisible(true); // show the window + + } + + SceneGraph() { + setOpaque(true); + setBackground(Color.WHITE); + + // make sure panel is focusable for getting key events + // (remember focus dispatch?) + setFocusable(true); + this.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + + switch(e.getKeyCode()) { + case KeyEvent.VK_RIGHT: + rotateBy += 5; + break; + case KeyEvent.VK_LEFT: + rotateBy -= 5; + break; + case KeyEvent.VK_UP: + scaleBy += .1; + break; + case KeyEvent.VK_DOWN: + scaleBy -= .1; + break; + + } + repaint(); + } + }); + } + + // scaleBy and rotation + double scaleBy = 2; + double rotateBy = 0; + + // custom graphics drawing + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + + + // draw the house in centre of screen + g2.translate(getWidth() / 2, getHeight()/ 2); + g2.rotate(Math.toRadians(rotateBy)); + g2.scale(scaleBy, scaleBy); + + g2.setStroke(new BasicStroke(3)); + g2.drawPolygon(houseShape.xpoints, houseShape.ypoints, houseShape.npoints); + + // save transform for later + AffineTransform save = g2.getTransform(); + + // remember: these are all in "house coordinates" + g2.translate(-25, 0); // window centred 25 px + g2.scale(0.4, 0.4); // window is 40% house width + drawWindow(g2); + + // translate to right 50 px + g2.translate(50 / 0.4, 0); + drawWindow(g2); + + // draw third window twice to demo different coordinate frames + + // set transform to saved matrix to return to "House" coordinates + // since this window is drawn in house coordinates, it will be + // transformed with the house + g2.setTransform(save); + drawWindow(g2, 0, -50, 45, 0.25); + + // set transform to identity to reset + // this means third window is drawn in World Coordinates + // and won't be transformed with the house + g2.setTransform(new AffineTransform()); + + // using function which has a model-to-world transform built in + drawWindow(g2, getWidth()/2, 89, 45, 0.5); + } + + // 100 x 100 house shape using Java Polygon structure + // (model position is centred at top left corner) + private Polygon houseShape = new Polygon(new int[] { -50, 50, 50, 0, -50}, + new int[] { 75, 75, -25, -75, -25}, 5); + + // draws 100 x 100 window shape centred at 0,0 + void drawWindow(Graphics2D g2) { + g2.setColor(Color.BLACK); + g2.fillRect(-50, -50, 100, 100); + g2.setColor(Color.WHITE); + g2.fillRect(-40, -40, 35, 35); + g2.fillRect(5, -40, 35, 35); + g2.fillRect(-40, 5, 35, 35); + g2.fillRect(5, 5, 35, 35); + } + + // draws 100 x 100 window shape centred at 0,0 + void drawWindow(Graphics2D g2, double x, double y, double theta, double s) { + + // save the current g2 transform matrix + AffineTransform save = g2.getTransform(); + + // do the model to world transformation + g2.translate(x, y); // T + g2.rotate(Math.toRadians(theta)); // R + g2.scale(s, s); // S + + // draws 100 x 100 window centred at 0,0 + drawWindow(g2); + // reset the transform to what it was before we drew the shape + g2.setTransform(save); + } +} diff --git a/java/2-8-Transformation/Transform2.java b/java/2-8-Transformation/Transform2.java new file mode 100644 index 0000000000000000000000000000000000000000..ba650cf01eef889052a85f8493fde2cd9403ce3a --- /dev/null +++ b/java/2-8-Transformation/Transform2.java @@ -0,0 +1,63 @@ +/* +* CS 349 Java Code Examples +* +* Transform2 Shows how to use Graphics2D matrix tranformations to transform a shape model. +* +*/ +import javax.swing.JFrame; +import javax.swing.JComponent; +import javax.swing.JButton; +import java.awt.*; +import java.awt.geom.*; +import java.util.ArrayList; +import javax.vecmath.*; +import java.lang.Math.*; +import java.util.Random; +import java.awt.event.*; + +// create the window and run the demo +public class Transform2 { + + public static void main(String[] args) { + // create the window + Canvas canvas = new Canvas(); + JFrame f = new JFrame("Transform2"); // jframe is the app window + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setSize(400, 400); // window size + f.setContentPane(canvas); // add canvas to jframe + f.setVisible(true); // show the window + } +} + +class Canvas extends JComponent { + + // the house shape (model position is centred at top left corner) + private Polygon shape = new Polygon(new int[] { -50, 50, 50, 0, -50}, + new int[] { 75, 75, -25, -75, -25}, 5); + + Point2d M = new Point2d(); + + Canvas() { + // events + this.addMouseMotionListener(new MouseAdapter(){ + public void mouseMoved(MouseEvent e){ + M.x = e.getX(); + M.y = e.getY(); + repaint(); + }}); + } + + // custom graphics drawing + public void paintComponent(Graphics g) { + super.paintComponent(g); // JPanel paint + Graphics2D g2 = (Graphics2D)g; + + // the shape will get transformed when rendered + g2.translate(M.x, M.y); + g2.rotate(45); + g2.scale(2, 1); + + g2.setStroke(new BasicStroke(3)); + g2.drawPolygon(shape.xpoints, shape.ypoints, shape.npoints); + } +} diff --git a/java/2-8-Transformation/makefile b/java/2-8-Transformation/makefile new file mode 100644 index 0000000000000000000000000000000000000000..06ac46ed1f6b3fa8ecf418952a5efd52b48eb0c9 --- /dev/null +++ b/java/2-8-Transformation/makefile @@ -0,0 +1,25 @@ +# super simple makefile +# call it using 'make NAME=name_of_code_file_without_extension' +# (assumes a .java extension) +NAME = Transform2 +# you may need to pass OS=win to run on windows +OS = + +# HACK: vecmath is included regardless if needed +all: + @echo "Compiling..." + javac -cp vecmath.jar $(NAME).java + +run: all +# windows needs a semicolon +ifeq ($(OS),win) + @echo "Running on windows ..." + java -cp "vecmath.jar;." $(NAME) +# everyone else likes a colon +else + @echo "Running ..." + java -cp "vecmath.jar:." $(NAME) +endif + +clean: + rm -rf *.class diff --git a/java/2-8-Transformation/shapemodel/Shape.java b/java/2-8-Transformation/shapemodel/Shape.java new file mode 100644 index 0000000000000000000000000000000000000000..716741f9d9f9c2cd2f4aaac3b956e7af8c59321b --- /dev/null +++ b/java/2-8-Transformation/shapemodel/Shape.java @@ -0,0 +1,151 @@ +/* +* Shape: See ShapeDemo for an example how to use this class. +* +*/ +import java.util.ArrayList; +import java.awt.*; +import java.awt.geom.*; +import javax.vecmath.*; + +// simple shape model class +class Shape { + + // shape points + ArrayList<Point2d> points; + + public void clearPoints() { + points = new ArrayList<Point2d>(); + pointsChanged = true; + } + + // add a point to end of shape + public void addPoint(Point2d p) { + if (points == null) clearPoints(); + points.add(p); + pointsChanged = true; + } + + // add a point to end of shape + public void addPoint(double x, double y) { + addPoint(new Point2d(x, y)); + } + + public int npoints() { + return points.size(); + } + + // shape is polyline or polygon + Boolean isClosed = false; + + public Boolean getIsClosed() { + return isClosed; + } + + public void setIsClosed(Boolean isClosed) { + this.isClosed = isClosed; + } + + // if polygon is filled or not + Boolean isFilled = false; + + public Boolean getIsFilled() { + return isFilled; + } + + public void setIsFilled(Boolean isFilled) { + this.isFilled = isFilled; + } + + // drawing attributes + Color colour = Color.BLACK; + float strokeThickness = 3.0f; + + public Color getColour() { + return colour; + } + + public void setColour(Color colour) { + this.colour = colour; + } + + public float getStrokeThickness() { + return strokeThickness; + } + + public void setStrokeThickness(float strokeThickness) { + this.strokeThickness = strokeThickness; + } + + // shape's transform + + // quick hack, get and set would be better + float scale = 1.0f; + + // some optimization to cache points for drawing + Boolean pointsChanged = false; // dirty bit + int[] xpoints, ypoints; + int npoints = 0; + + void cachePointsArray() { + xpoints = new int[points.size()]; + ypoints = new int[points.size()]; + for (int i=0; i < points.size(); i++) { + xpoints[i] = (int)points.get(i).x; + ypoints[i] = (int)points.get(i).y; + } + npoints = points.size(); + pointsChanged = false; + } + + + // let the shape draw itself + // (note this isn't good separation of shape View from shape Model) + public void draw(Graphics2D g2) { + + // don't draw if points are empty (not shape) + if (points == null) return; + + // see if we need to update the cache + if (pointsChanged) cachePointsArray(); + + // save the current g2 transform matrix + AffineTransform M = g2.getTransform(); + + // multiply in this shape's transform + // (uniform scale) + g2.scale(scale, scale); + + // call drawing functions + g2.setColor(colour); + if (isFilled) { + g2.fillPolygon(xpoints, ypoints, npoints); + } else { + // can adjust stroke size using scale + g2.setStroke(new BasicStroke(strokeThickness / scale)); + if (isClosed) + g2.drawPolygon(xpoints, ypoints, npoints); + else + g2.drawPolyline(xpoints, ypoints, npoints); + } + + // reset the transform to what it was before we drew the shape + g2.setTransform(M); + } + + + // let shape handle its own hit testing + // (x,y) is the point to test against + // (x,y) needs to be in same coordinate frame as shape, you could add + // a panel-to-shape transform as an extra parameter to this function + // (note this isn't good separation of shape Controller from shape Model) + public boolean hittest(double x, double y) + { + if (points != null) { + + // TODO Implement + + } + + return false; + } +} diff --git a/java/2-8-Transformation/shapemodel/ShapeDemo.java b/java/2-8-Transformation/shapemodel/ShapeDemo.java new file mode 100644 index 0000000000000000000000000000000000000000..eb1ebc19f366bc7b2e1195b5ecb8e4d76987a842 --- /dev/null +++ b/java/2-8-Transformation/shapemodel/ShapeDemo.java @@ -0,0 +1,65 @@ +/* +* CS 349 Java Code Examples +* +* ShapeDemo Demo of Shape class: draw shapes using mouse. +* +*/ +import javax.swing.*; +import java.awt.*; +import java.awt.geom.*; +import java.awt.event.*; + +import javax.vecmath.*; + +// create the window and run the demo +public class ShapeDemo extends JPanel { + + Shape shape; + + ShapeDemo() { + + this.addMouseListener(new MouseAdapter(){ + public void mousePressed(MouseEvent e) { + + shape = new Shape(); + // change shape type + // shape.setIsClosed(true); + // shape.setIsFilled(true); + shape.setColour(Color.BLUE); + + // try setting scale to something other than 1 + shape.scale = 1.0f; + + repaint(); + } + }); + + this.addMouseMotionListener(new MouseAdapter(){ + public void mouseDragged(MouseEvent e) { + shape.addPoint(e.getX(), e.getY()); + repaint(); + } + }); + } + + public static void main(String[] args) { + // create the window + ShapeDemo canvas = new ShapeDemo(); + JFrame f = new JFrame("ShapeDemo"); // jframe is the app window + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setSize(300, 300); // window size + f.setContentPane(canvas); // add canvas to jframe + f.setVisible(true); // show the window + } + // custom graphics drawing + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; // cast to get 2D drawing methods + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // antialiasing look nicer + RenderingHints.VALUE_ANTIALIAS_ON); + + if (shape != null) + shape.draw(g2); + } +} + diff --git a/java/2-8-Transformation/shapemodel/makefile b/java/2-8-Transformation/shapemodel/makefile new file mode 100644 index 0000000000000000000000000000000000000000..1a5cedbb7bb4d0df60de3e9efa9b0b4465077824 --- /dev/null +++ b/java/2-8-Transformation/shapemodel/makefile @@ -0,0 +1,26 @@ +# super simple makefile +# call it using 'make NAME=name_of_code_file_without_extension' +# (assumes a .java extension) +NAME="ShapeDemo" +# you may need to pass OS=win to run on windows +OS = + +# HACK: vecmath is included regardless if needed +all: + @echo "Compiling..." + javac -cp vecmath.jar *.java + +run: all +# windows needs a semicolon +ifeq ($(OS),win) + @echo "Running on windows ..." + java -cp "vecmath.jar;." $(NAME) +# everyone else likes a colon +else + @echo "Running ..." + java -cp "vecmath.jar:." $(NAME) +endif + +clean: + rm -rf *.class + diff --git a/java/2-8-Transformation/shapemodel/vecmath.jar b/java/2-8-Transformation/shapemodel/vecmath.jar new file mode 100644 index 0000000000000000000000000000000000000000..6d8b3a11c9c7a02d736a2ccc7f07fa70eb319832 Binary files /dev/null and b/java/2-8-Transformation/shapemodel/vecmath.jar differ diff --git a/java/2-8-Transformation/vecmath.jar b/java/2-8-Transformation/vecmath.jar new file mode 100644 index 0000000000000000000000000000000000000000..6d8b3a11c9c7a02d736a2ccc7f07fa70eb319832 Binary files /dev/null and b/java/2-8-Transformation/vecmath.jar differ