/** Company: Shout3D LLC Project: Shout3D 2.0 Sample Code Class: PyramidTestPanel Date: April 26, 1999 Description: Class for panel in which you click-drag to scale some pyramids (C) Copyright Shout3D LLC. - 1997-2001 - All rights reserved */ package applets; //Include this in order to be able to work //with nodes in the custom_nodes package import custom_nodes.*; import shout3d.*; import shout3d.core.*; /** * PyramidTestPanel * * Shows how custom nodes can be added to Shout3D * and used in applets. * * This applet creates three Pyramid nodes (a custom node in the * custom_nodes directory) and then changes their fields when * the user click-drags them. * * * @author Dave Westwood * @author Paul Isaacs * @author Jim Stewartson */ public class PyramidTestPanel extends Shout3DPanel implements DeviceObserver { // 3 Pyramids to be displayed and manipulated in the scene. Pyramid pyramid0, pyramid1, pyramid2; // For determining which pyramid is clicked with the mouse. Picker myPicker; Node[] pathToPick; /** * Constructor */ public PyramidTestPanel(Shout3DApplet applet) { super(applet); } float[] lavender = { .6f, .6f, 1 }; float[] seafoam = { .6f, 1f, .6f }; float[] gray = { .6f, .6f, .6f }; float[] pos0 = { -4, 0, 0 }; float[] pos1 = { 0, 0, 0 }; float[] pos2 = { 4, 0, 0 }; float[] rot0 = { 0, 1, 0, .8f }; float[] rot1 = { 0, 1, 0, 0 }; float[] rot2 = { 0, 1, 0, -.8f }; /** * * This method is automatically called by the parent class Shout3DPanel * at the correct time during initialize(). * * Subclasses should implement this to perform any custom initialization tasks. */ public void customInitialize() { // Create the three pyramids pyramid0 = new Pyramid(); pyramid1 = new Pyramid(); pyramid2 = new Pyramid(); // Get a transform for each pyramid, passing in pretty colors Transform xf0 = createXf( pyramid0, lavender ); Transform xf1 = createXf( pyramid1, seafoam ); Transform xf2 = createXf( pyramid2, gray ); // Position them relative to each other: xf0.translation.setValue(pos0); xf1.translation.setValue(pos1); xf2.translation.setValue(pos2); xf0.rotation.setValue(rot0); xf1.rotation.setValue(rot1); xf2.rotation.setValue(rot2); // Add to the scene Node[] newKids = new Node[3]; newKids[0] = xf0; newKids[1] = xf1; newKids[2] = xf2; getScene().addChildren(newKids); // Move the camera back Viewpoint myVP = (Viewpoint) getCurrentBindableNode("Viewpoint"); myVP.position.getValue()[2] = 10; //notify myVP.position.setValue(myVP.position.getValue()); // Allocate the picker myPicker = getNewPicker(); //Watch for mouse events to do the picking. this.addDeviceObserver(this, "MouseInput", null); //Register to watch rendering. We'll want to change pyramid //fields between renders so that field don't change during //mid-multithreaded-render getRenderer().addRenderObserver(this,null); // Call the parent class. // Technically not required since parent class' method does nothing. // But good practice because in classes derived from this one, // it will be necessary to call the superclass to get the // functionality herein. // Hence keeping the call here is good practice, so that if this // method is copied/pasted in a subclass, all will be well. super.customInitialize(); } /** * Finalize */ protected void finalize()throws Throwable { getRenderer().removeRenderObserver(this); this.removeDeviceObserver(this, "MouseInput"); // Call the parent class. // Technically not required since parent class' method does nothing. // But good practice because in classes derived from this one, // it will be necessary to call the superclass to get the // functionality herein. // Hence keeping the call here is good practice, so that if this // method is copied/pasted in a subclass, all will be well. super.finalize(); } Node pathTail; Pyramid selectedPyramid; float startWidth, startHeight, startDepth; int startCursorX, startCursorY; boolean waitingToChangeFields = false; float nextWidth = 1; float nextHeight = 1; float nextDepth = 1; /** * Called when Mouse input is received. * * On mouse DOWN, checks for selection of one of the pyramids. * Remembers which pyramid, and starting cursor location. * * On mouse DRAG, scales the selected pyramid. * Horizontal motion scales width,depth. * Vertical motion scales height. * */ public boolean onDeviceInput(DeviceInput di, Object userData) { //No need to check type of deviceInput, only registered for Mouse Input. MouseInput mi = (MouseInput) di; if (mi.which == MouseInput.DOWN) { // Perform a pick and see if one of the pyramids was picked. // selectedPyramid = null; pathToPick = myPicker.pickClosest(mi.x,mi.y); if (pathToPick!=null && pathToPick.length > 0) { pathTail = pathToPick[pathToPick.length-1]; if (pathTail instanceof Pyramid) { selectedPyramid = (Pyramid) pathTail; startCursorX = mi.x; startCursorY = mi.y; startWidth = selectedPyramid.width.getValue(); startHeight = selectedPyramid.height.getValue(); startDepth = selectedPyramid.depth.getValue(); //This input was used return true; } } } else if (mi.which == MouseInput.DRAG) { // Based on mouse motion, change fields of currentPyramid. // Width and depth change equally with horizontal motion, // Height changes with vertical motion. // Change by 1 unit every 100 pixels nextWidth = startWidth + (float)(mi.x-startCursorX)/100; nextHeight = startHeight - (float)(mi.y-startCursorY)/100; nextDepth = startDepth + (float)(mi.x-startCursorX)/100; // Clamp values to be > 0.1 if (nextWidth < 0.1) nextWidth = 0.1f; if (nextHeight < 0.1) nextHeight = 0.1f; if (nextDepth < 0.1) nextDepth = 0.1f; // Don't set the field values now. // Let them be set during onPreRender so that // the field change occurs between renders. waitingToChangeFields = true; // Used the input return true; } //Did not care about this input return false; } /** * Changing the fields should be done between renders. * This is because rendering happens in a different thread, * and updating the fields during a render can cause unpleasant * flashing. * * Note that no onPostRender() method is implemented in this class, * even though it is required for all classes implementing RenderObserver. * This is because the super class implements RenderObserver fully * and onPostRender is inherited. * Hence this class needs only to override the onPreRender() method, * making sure to call super.onPreRender(r,userData) within the body * of the method. */ public void onPreRender(Renderer r, Object userData) { // Call the parent class. // Technically not required since parent class' method does nothing. // But good practice because in classes derived from this one, // it will be necessary to call the superclass to get the // functionality herein. // Hence keeping the call here is good practice, so that if this // method is copied/pasted in a subclass, all will be well. super.onPreRender(r, userData); if (selectedPyramid != null && waitingToChangeFields){ selectedPyramid.width.setValue(nextWidth); selectedPyramid.height.setValue(nextHeight); selectedPyramid.depth.setValue(nextDepth); waitingToChangeFields = false; } } Transform createXf( Pyramid pyr, float[/*3*/] color ) { Material newMat = new Material(); newMat.diffuseColor.setValue(color); Appearance newApp = new Appearance(); newApp.material.setValue(newMat); shout3d.core.Shape newShape = new shout3d.core.Shape(); newShape.geometry.setValue(pyr); newShape.appearance.setValue( new Appearance[] { newApp }); Transform newXf = new Transform(); Node[] kids = new Node[1]; kids[0] = newShape; newXf.addChildren(kids); return newXf; } }