// This application renders volumes from a CMS DDD or Geant3 rz/fz file.
// It also can display events, from a flat file.
//
// (c) J.J.Bunn, Caltech 2000,2001,2002,2003,2004.



import java.net.*;


import com.sun.j3d.utils.universe.*;
import com.sun.j3d.utils.behaviors.mouse.*;
import com.sun.j3d.utils.behaviors.picking.*;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.Sphere;

import java.applet.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.*;
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.JFrame;
import javax.swing.tree.*;

//import ncsa.j3d.ui.record.*;
//import vrml.j3d.*;


public class COJAC extends JFrame implements Runnable {

	public static String	READER = "DDD";

	public Thread			thread;
	public boolean			twisting = false;
	public boolean			lighting = true;
	public boolean			sampleShapes = false;

	//public G3				geantReader = null;

	public DDD				DDDReader = null;

	boolean					geometryRead = false;
	boolean					rotationsRead = false;

	Matrix3d []				rotM;
	Shape3D []				volShapes = null;

	int						nVolumes;


	public int				nDetector = 0;
	public int				detailLevel = 3;
	public int				maxLevelSeen;
	public String			detectorInitials = "";

	// For colouring and appearance
	public String			appInitials = "SPGQOCBTMRYHEV";
	public int				nDets = appInitials.length()+1;
	public Appearance []	solidAppearance = new Appearance[nDets];
	public Appearance []	wireAppearance = new Appearance[nDets];

	public Appearance		invisibleAppearance;

	public String			visibleDetectors = "";

	
	PrintWriter				dumpfile = null;

	String					datafile = "dumppart.txt";

	String					cmsimFile = "cms132.rz";

	float deg2rad = (float) (Math.PI / 180.);

/*
	// SOAP stuff
	final String endPoint = "http://baldrick.cacr.caltech.edu:80/TagServer/TagServices.asmx";
	final String nameSpace = "http://baldrick.cacr.caltech.edu/TagServer/";
	final String proxy = "http://localhost:8080";

	SoapConfig sc = null;
	RPCCall call = null;
	SoapHttpClient shc = null;
	// End of SOAP stuff
*/



	String					dataAccess = "SOAP";


	int						CurrentEvent = 0;
	int						CurrentRun = 0;
	Event					thisEvent = null;

	int						networkSize = 100; // size in kBytes for network tests

	Container				frameContainer;

	JTree					geometryTree;
	JTree					eventTree = null;
	JPanel					PEventTree;
	JPanel					PGeometryTree;
	JScrollPane				scrollPaneEvent, scrollPaneGeometry;
	JMenuBar				menuBar;
	JMenu					detectorMenu = null;
	JLabel					LEventNumber, LRunNumber, LTrackCount, LJets, LParticles, LPhotons;
	JLabel					LTrackerCount, LEcalCount, LHcalCount, LMuonCount;
	JLabel					LEcalClusterCount, LHcalClusterCount;
	JLabel					LMessage;

	JTextArea				TConsole;

	JButton					BNext, BPrev, BSide, BEnd, BLeft, BRight, BZoomIn, BZoomOut, BLights, BIdle, BDump, BJPEG, BDisplay, BNetwork;

	JCheckBox				CBoxBeam,CBoxTrak,CBoxPixel,CBoxSiVertex,CBoxEcalBarrel,CBoxEcalForward,CBoxHcal,
							CBoxMuonBarrel,CBoxMuonForward,CBoxVcal;

	JCheckBoxMenuItem		XTracker, XEcal, XHcal, XCoil, XVfcal, XMuon;
	JCheckBoxMenuItem		XTracker_Raw, XEcal_Raw, XHcal_Raw, XCoil_Raw, XVfcal_Raw, XMuon_Raw;
	JCheckBoxMenuItem		XTrackFits;

	SimpleUniverse Universe;
	TransformGroup viewTransform;
	Shape3D dummyShape;

	Vector vectorPositionals;
	
	BranchGroup objRoot;

	BranchGroup detectorBG=null;
	Vector detectorNames;
	Vector detectorBGs;
	Vector detectorTNs;
	Vector parentNodes;

	BranchGroup eventBG=null;
	BranchGroup twistBG=null;
	BranchGroup lightsBG=null;
	TransformGroup CMSTrans;
	Transform3D t3d;
	BoundingSphere bounds;
	//RecordableCanvas3D canvasCMS;
	Canvas3D canvasCMS;
	Font3D f3d;
	String fontName = "Helvetica";

	Scrollbar SZoom;

	public static void main(String argv[]) {
		COJAC app = new COJAC();
	}

	public COJAC() {

		super("The CMS Ohmygoodness Java Analysis Component (COJAC)");

		setSize(1000,700);

		addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
        });


		geometryRead = false;
		rotationsRead = false;


		//geantReader = new G3();

		DDDReader = new DDD();

		setupAppearances();

		start();

	}

	public void start() {

        thread = new Thread(this);
        thread.setPriority(Thread.MIN_PRIORITY);
        thread.setName("COJAC");
        thread.start();

		buildGUI();

	}

	public void run() {

		do {
			//System.out.print("!");
			try{
				Thread.sleep(100);
			} catch (Exception e) {
			}
		} while (true);

	}

    public synchronized void stop() {
        thread = null;
    }

	public void TextMessage(String S) {
		System.out.println(S);
		if(TConsole != null) TConsole.append(S+"\n");
	}

	boolean accumulateEventsWS(int kbytesNeeded) {
		// This routine keeps on fetching Tag event objects from
		// the database until a certain data size is reached (kbytesNeeded)
		TextMessage("Running Network Test");
		if(eventTree == null) {
			createEventTreeWS(); // creates the list of events available from the Web Service
		}

		int bytesReceived = 0;
		int bytesNeeded = 1000*kbytesNeeded;

		int tagSize = 827; // this is the size in bytes of the SOAP return message for a single event

		Appearance sAppearance =  new Appearance();
		//sAppearance.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_WRITE);
		ColoringAttributes sColoring = new ColoringAttributes();
		//sColoring.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
		sColoring.setColor(new Color3f(0.5f,0.5f,1.0f));
		sAppearance.setColoringAttributes(sColoring);

		if(eventBG != null) {
			eventBG.detach();
			eventBG = null;
			System.gc();
		}

		eventBG = new BranchGroup();
	    eventBG.setCapability(BranchGroup.ALLOW_DETACH);
	    eventBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
	    eventBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
	    eventBG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);

		CMSTrans.addChild(eventBG);


		long timestart = System.currentTimeMillis();

		System.out.print("Fetching "+kbytesNeeded+" kBytes of Tag objects ");

		int count = 0;

		while(bytesReceived < bytesNeeded) {

			System.out.print("|");

			TreePath path = eventTree.getLeadSelectionPath();
			eventTree.expandPath(path);
			int i=0;
			while(i<eventTree.getRowCount()) {
				path = eventTree.getPathForRow(i++);
				eventTree.expandPath(path);
			}

			int nrows = eventTree.getRowCount();

			TextMessage(nrows+" event rows available");
	
			if(nrows <= 0) return false; // no events in the Tree :-(
		
			for(i=0;i<nrows;i++) {

				path = eventTree.getPathForRow(i);

				//TextMessage("i="+i+" tree "+path);

				if(path.getPathCount() != 3) continue;

				int therun = Integer.valueOf(path.getPathComponent(1).toString().substring(4)).intValue();
				int theevent = Integer.valueOf(path.getPathComponent(2).toString().substring(6)).intValue();

				//TextMessage("Selecting event "+theevent+" from run "+therun);

				Tag tagEvent = selectEventWS(therun,theevent);

				
				
				if(tagEvent == null) continue;


				bytesReceived += tagSize;
				if(bytesReceived % 1000 == 0) System.out.print(".");

				count++;
					
				double energy = tagEvent.getJetE(0);
				if(energy > 0.0 && count % 100 == 0) {

						float Extent = 1000.0f*(float)energy/50.f;

						double theta = tagEvent.getJetTheta(0);
						double phi = tagEvent.getJetPhi(0);
						float tanth = (float) Math.tan(theta);
						float sinphi = (float) Math.sin(phi);
						float cosphi = (float) Math.cos(phi);
						float X = Extent*cosphi;
						float Y = Extent*sinphi;
						float Z = Extent/tanth;

						BranchGroup bg = new BranchGroup();
						TransformGroup tg = new TransformGroup();
						bg.addChild(tg);

						Transform3D t3d = new Transform3D();

						CMSTrans.getTransform(t3d);

						Vector3d translation = new Vector3d();

						t3d.get(translation);

						translation.x += X;
						translation.y += Y;
						translation.z += Z;

						t3d.setTranslation(translation);

						tg.setTransform(t3d);

						//sColoring.setColor(new Color3f((float)Math.random(),(float)Math.random(),(float)Math.random()));
						//sAppearance.setColoringAttributes(sColoring);
						
						Sphere blob = new Sphere(10.0f, Sphere.GENERATE_NORMALS, 10, sAppearance);

						tg.addChild(blob);

						eventBG.addChild(bg);

				}
				if(bytesReceived >= bytesNeeded) break;
			}
		}

		long timefinish = System.currentTimeMillis();

		float kBps = (float) bytesReceived / (float) (timefinish-timestart);

		TextMessage("\nRead "+(bytesReceived/1000)+" kBytes at "+kBps+" kBytes/sec");

		return true;

	}


	void createEventTreeWS() {

/*

		int countE = 0;

		JPanel PCount = new JPanel(new GridLayout(1,1));
		JLabel LEventCount = new JLabel("0 Events Available");
		PCount.add(LEventCount);

		PEventTree.add(PCount,"South");

		scrollPaneEvent = new JScrollPane();
		
		DefaultMutableTreeNode eventDBTree = new DefaultMutableTreeNode("Events");
		DefaultMutableTreeNode run=null;
		DefaultMutableTreeNode event=null;

		String methodName = "GetRunNumbers";
		
		sc = new SoapConfig();
		// set the actor for fault messages
		sc.setActor("/sample/client");		

		// prepare a call
		call = new RPCCall(	sc,
							nameSpace, 
							methodName);

		try {
			// execute call (via HTTP)
		    // SoapHttpClient shc = new SoapHttpClient(sc,new URL(proxy));	
		    SoapHttpClient shc = new SoapHttpClient(sc);	

		    Envelope answer = shc.invokeHTTP(
		             new URL(endPoint),
		             nameSpace+methodName,  
		             call.getEnvelope()); 
				
			// check for a fault message
			
			if(answer.hasFault()) {
				Fault f = answer.getFault();
				TextMessage("Faultcode:   " + f.getFaultcode());
				TextMessage("Faultstring: " + f.getFaultstring());
				TextMessage("Faultactor:  " + f.getFaultactor());		
				return;
			}
					
			// get the results
			ParamBag p = call.getResults(answer);
				
			SoapArray SR = (SoapArray) p.getValue(0);

			int numRuns = SR.getLength(); // Should be the same as from GetNumRuns

			TextMessage("Number of Runs "+numRuns);

			for(int i=0;i<numRuns;i++) {
				int runNumber = SR.getInt(i);
				TextMessage("Run num: "+runNumber);
				run = new DefaultMutableTreeNode("Run "+runNumber);
				eventDBTree.add(run);

				methodName = "GetEventNumbers";

				call = new RPCCall(sc,nameSpace,methodName);
				call.setParameter(	"runNumber", 
									""+runNumber,
									Integer.class);

				answer = shc.invokeHTTP(new URL(endPoint), nameSpace+methodName, call.getEnvelope());

				if(answer.hasFault()) {
					Fault f = answer.getFault();
					TextMessage("Faultcode:   " + f.getFaultcode());
					TextMessage("Faultstring: " + f.getFaultstring());
					TextMessage("Faultactor:  " + f.getFaultactor());		
					return;
				}
	
				p = call.getResults(answer);

				SoapArray SE = (SoapArray) p.getValue(0);

				int numEvents = SE.getLength();

				for(int j=0;j<numEvents;j++) {
					countE++;
					int eventNumber = SE.getInt(j);
					event = new DefaultMutableTreeNode("Event "+eventNumber);
					run.add(event);
					if(countE % 100 == 0) System.out.print(".");
				}

			}

		}
		catch(Exception e) {
			TextMessage("Exception: " + e);
			e.printStackTrace();
			return;
		}	
		
		TextMessage(" ");

		LEventCount.setText(countE+" Events Available");

		eventTree = new JTree(eventDBTree);
		treeRenderer renderer = new treeRenderer();
		eventTree.setCellRenderer(renderer);
		eventTree.addTreeSelectionListener(new eventHandler());

		scrollPaneEvent.getViewport().add(eventTree);
		PEventTree.add(scrollPaneEvent,"Center");

		*/
	}





	void createEvent(String type) {

		BufferedReader BR = null;

		int nParticles = 0;
		int nJets = 0;

		try {
			BR = new BufferedReader(new InputStreamReader(new FileInputStream(datafile)));

			String S = BR.readLine();

			StringTokenizer ST = new StringTokenizer(S);

			int run = Integer.valueOf(ST.nextToken()).intValue();
			int event = Integer.valueOf(ST.nextToken()).intValue();

			LEventNumber.setText("Event "+event);
			LRunNumber.setText("Run "+run);

			if(eventBG != null) eventBG.detach();

			eventBG = new BranchGroup();
		    eventBG.setCapability(BranchGroup.ALLOW_DETACH);
		    eventBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
		    eventBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);

			while( (S = BR.readLine()) != null) {

				if(type.equals("Tag") && nParticles >= 5) break;

				nParticles++;

				ST = new StringTokenizer(S);

				double dx = Double.valueOf(ST.nextToken()).doubleValue();
				double dy = Double.valueOf(ST.nextToken()).doubleValue();
				double dz = Double.valueOf(ST.nextToken()).doubleValue();
				double ee = Double.valueOf(ST.nextToken()).doubleValue();

				TextMessage("Particle/jet "+dx+" "+dy+" "+dz+" "+ee);

				int id = Integer.valueOf(ST.nextToken()).intValue();

				LineArray trackGeometry = new LineArray (2, LineArray.COORDINATES);
				trackGeometry.setCapability(LineArray.ALLOW_COLOR_WRITE);
				trackGeometry.setCapability(LineArray.ALLOW_COORDINATE_WRITE);
				trackGeometry.setCapability(LineArray.ALLOW_COORDINATE_READ);
				trackGeometry.setCapability(LineArray.ALLOW_COUNT_READ);
				trackGeometry.setCapability(LineArray.ALLOW_FORMAT_READ);

				double [] uverts = new double[6];

				uverts[0] = 0.0;
				uverts[1] = 0.0;
				uverts[2] = 0.0;

				uverts[3] = 100.*dx;
				uverts[4] = 100.*dy;
				uverts[5] = 10.*dz;

				trackGeometry.setCoordinates(0, uverts);

				float cRed = (float) (ee/30);
				if(cRed > 1.0f) cRed = 1.0f;

				Appearance trackAppearance =  new Appearance();
				TransparencyAttributes trackTrans =new TransparencyAttributes(TransparencyAttributes.FASTEST, 0.3f);
				trackAppearance.setTransparencyAttributes(trackTrans);
				ColoringAttributes trackColoring = new ColoringAttributes();
				trackColoring.setColor(new Color3f(cRed,0.0f,1.0f-cRed));
				trackAppearance.setColoringAttributes(trackColoring);

				Shape3D track = new Shape3D(trackGeometry,trackAppearance);
				eventBG.addChild(track);

			}

		} catch (Exception e) {
			System.err.println("Error reading "+datafile);
			return;
		}


		LParticles.setText(nParticles+" Particles");

		CMSTrans.addChild(eventBG);


	}

	class treeRenderer extends JLabel implements TreeCellRenderer {
		public Component getTreeCellRendererComponent(JTree tree,
			Object value, boolean selected, boolean expanded, boolean leaf,
			int row, boolean hasFocus) {

			if(tree.equals(eventTree)) {
				setForeground(Color.black);
				setText(value.toString());	
				return this;
			}

			DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;

			String text = value.toString();
			
			setForeground(Color.blue);


			if(!text.equals("Detectors")) {

				// Following code used to decide on color of node in tree
				int index = detectorTNs.indexOf(node);

				if(index != -1) {
					BranchGroup thisBG = (BranchGroup) detectorBGs.elementAt(index);
					if(thisBG != null) {
						String userData = (String) thisBG.getUserData();
						if( userData != null) if(!userData.equals("Attached")) setForeground(Color.gray);
					} else {
						TextMessage("No BG!");
					}
				}

			}
			setText(text);
			return this;
		}
	}


	void createViewer() {

		JPanel PTrees = new JPanel(new GridLayout(3,1));

		//PEventTree = new JPanel(new BorderLayout());

		PGeometryTree = new JPanel(new BorderLayout());

		JPanel PConsole = new JPanel(new BorderLayout());

		scrollPaneGeometry = new JScrollPane();
		scrollPaneEvent = new JScrollPane();

/*
		JMenu rawdataMenu = new JMenu("Raw Data");

		// The lightweight stuff below should avoid problems with menus being
		// obscured by the Canvas3D ...
		JPopupMenu popupMenu = rawdataMenu.getPopupMenu();
		popupMenu.setDefaultLightWeightPopupEnabled(false);

		menuBar.add(rawdataMenu);

		XTracker_Raw = new JCheckBoxMenuItem("Hits",false);
		XTracker_Raw.addItemListener(new eventHandler());
		rawdataMenu.add(XTracker_Raw);

		XEcal_Raw = new JCheckBoxMenuItem("Crystals",false);
		XEcal_Raw.addItemListener(new eventHandler());
		rawdataMenu.add(XEcal_Raw);

		XHcal_Raw = new JCheckBoxMenuItem("Cells",false);
		XHcal_Raw.addItemListener(new eventHandler());
		rawdataMenu.add(XHcal_Raw);

		XVfcal_Raw = new JCheckBoxMenuItem("VCells",false);
		XVfcal_Raw.addItemListener(new eventHandler());
		rawdataMenu.add(XVfcal_Raw);

		XMuon_Raw = new JCheckBoxMenuItem("MHits",false);
		XMuon_Raw.addItemListener(new eventHandler());
		rawdataMenu.add(XMuon_Raw);
*/

		JPanel pCMS = new JPanel();
		pCMS.setLayout(new GridLayout(2,1));

/*
		JPanel pMessages = new JPanel();
		pMessages.setLayout(new GridLayout(2,5));

		LEventNumber = new JLabel("No Event");
		pMessages.add(LEventNumber);

		LRunNumber = new JLabel("No Run");
		pMessages.add(LRunNumber);

		LJets = new JLabel("No Jets");
		pMessages.add(LJets);

		LParticles = new JLabel("No Particles");
		pMessages.add(LParticles);

		LTrackCount = new JLabel("No Tracks");
		pMessages.add(LTrackCount);

		LTrackerCount = new JLabel("No Tracker digits");
		pMessages.add(LTrackerCount);

		LEcalCount = new JLabel("No ECAL cells");
		pMessages.add(LEcalCount);

		LHcalCount = new JLabel("No HCAL cells");
		pMessages.add(LHcalCount);

		LEcalClusterCount = new JLabel("No ECAL clusters");
		pMessages.add(LEcalClusterCount);

		LHcalClusterCount = new JLabel("No HCAL clusters");
		pMessages.add(LHcalClusterCount);

		LMuonCount = new JLabel("No Muon digits");
		pMessages.add(LMuonCount);

		pCMS.add(pMessages);
*/


		// The panel showing the selected detectors
		JPanel pCMSdet = new JPanel(new GridLayout(1,10));

		CBoxBeam = new JCheckBox("Beam Tube",true);
		pCMSdet.add(CBoxBeam);

		CBoxTrak = new JCheckBox("Tracker",false);
		pCMSdet.add(CBoxTrak);

		CBoxSiVertex = new JCheckBox("SiVertex",false);
		pCMSdet.add(CBoxSiVertex);

		CBoxPixel = new JCheckBox("Pixels",false);
		pCMSdet.add(CBoxPixel);

		CBoxEcalBarrel = new JCheckBox("ECAL Barrel",false);
		pCMSdet.add(CBoxEcalBarrel);

		CBoxEcalForward = new JCheckBox("ECAL Forward",false);
		pCMSdet.add(CBoxEcalForward);

		CBoxHcal = new JCheckBox("HCAL",false);
		pCMSdet.add(CBoxHcal);

		CBoxMuonBarrel = new JCheckBox("Muon Barrel",false);
		pCMSdet.add(CBoxMuonBarrel);

		CBoxMuonForward = new JCheckBox("Muon Forward",false);
		pCMSdet.add(CBoxMuonForward);

		CBoxVcal = new JCheckBox("VCAL",false);
		pCMSdet.add(CBoxVcal);


		pCMS.add(pCMSdet);

		BDisplay = new JButton("Display Selected Detectors");
		BDisplay.setForeground(Color.red);
		BDisplay.addActionListener(new eventHandler());
		pCMS.add(BDisplay);


		JPanel PView = new JPanel();
		PView.setLayout(new GridLayout(5,2));

		BSide = new JButton("Side View");
		BSide.addActionListener(new eventHandler());
		PView.add(BSide);

		BEnd  = new JButton("End View");
		BEnd.addActionListener(new eventHandler());
		PView.add(BEnd);

		BLeft  = new JButton("Left");
		BLeft.addActionListener(new eventHandler());
		PView.add(BLeft);

		BRight  = new JButton("Right");
		BRight.addActionListener(new eventHandler());
		PView.add(BRight);

		BZoomIn = new JButton("Zoom In");
		BZoomIn.addActionListener(new eventHandler());
		PView.add(BZoomIn);

		BZoomOut = new JButton("Zoom Out");
		BZoomOut.addActionListener(new eventHandler());
		PView.add(BZoomOut);

		BLights = new JButton("Lights Off");
		BLights.addActionListener(new eventHandler());
		PView.add(BLights);

		BIdle = new JButton("Idle");
		BIdle.addActionListener(new eventHandler());
		PView.add(BIdle);

		BDump = new JButton("Dump");
		BDump.addActionListener(new eventHandler());
		PView.add(BDump);

		BJPEG = new JButton("JPEG");
		BJPEG.addActionListener(new eventHandler());
		PView.add(BJPEG);


		JPanel PNetwork = new JPanel(new BorderLayout());

/*
		JLabel jl = new JLabel("Select download size");
		jl.setHorizontalTextPosition(SwingConstants.CENTER);
		PNetwork.add(jl,"North");

		JPanel PNetworkSize = new JPanel(new GridLayout(2,2));
		ButtonGroup bgrp = new ButtonGroup();

		JRadioButton rb = new JRadioButton("100 kBytes",true);
		rb.addActionListener(new eventHandler());
		PNetworkSize.add(rb);
		bgrp.add(rb);

		rb = new JRadioButton("1 MBytes",false);
		rb.addActionListener(new eventHandler());
		PNetworkSize.add(rb);
		bgrp.add(rb);

		rb = new JRadioButton("10 MBytes",false);
		rb.addActionListener(new eventHandler());
		PNetworkSize.add(rb);
		bgrp.add(rb);

		rb = new JRadioButton("100 MBytes",false);
		rb.addActionListener(new eventHandler());
		PNetworkSize.add(rb);
		bgrp.add(rb);
		
		PNetwork.add(PNetworkSize,"Center");

		BNetwork = new JButton("Start Network Test");
		BNetwork.addActionListener(new eventHandler());

		PNetwork.add(BNetwork,"South");

		PEventTree.add(PNetwork,"North");
*/

		TConsole = new JTextArea(20,20);
		JScrollPane scrollTextArea = new JScrollPane();
		scrollTextArea.getViewport().add(TConsole);
		PConsole.add(scrollTextArea,"Center");
		PConsole.setVisible(true);


		PTrees.add(PView);
		PTrees.add(PGeometryTree);
		PTrees.add(PConsole);



		GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D();

		GraphicsDevice dev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); 

		GraphicsConfiguration config = dev.getBestConfiguration(template);

 		canvasCMS = new Canvas3D(config);

		//canvasCMS = new RecordableCanvas3D(config,true);
		//canvasCMS.captureInPostRender(true);
		//canvasCMS.setDirectoryName("");
		//canvasCMS.setFilePrefix("COJAC_Frame");

		frameContainer.add(canvasCMS,"Center");
		frameContainer.add(pCMS,"North");
		//frameContainer.add(LMessage,"South"); // South empty
		frameContainer.add(PTrees,"West");

		setVisible(true);

		createScene();

		READER = "DDD";
		geometryRead = false;
		rotationsRead = false;
		destroyGeometry(detectorBG);
		createDetectorBG();
		CMSTrans.addChild(detectorBG);
		createDetectorTree();
		createDetectorMenu();

		PGeometryTree.setVisible(true);

		System.gc();
		
	}

	void createDetectorMenu() {

		// Create menu items for each Detector

		if(detectorMenu != null) menuBar.remove(detectorMenu);

		detectorMenu = new JMenu("Solids");

		TextMessage("Adding menu for "+detectorInitials);

		for(int i=0;i<detectorInitials.length();i++) {
			String text = "Detector "+detectorInitials.substring(i,i+1);
			JCheckBoxMenuItem jD = new JCheckBoxMenuItem(text,true);
			jD.addItemListener(new eventHandler());
			detectorMenu.add(jD);
		}

		menuBar.add(detectorMenu);
		JPopupMenu popupMenu = detectorMenu.getPopupMenu();
		popupMenu.setDefaultLightWeightPopupEnabled(false);
	}
	
	void destroyGeometry(BranchGroup bg) {
		int nchild = bg.numChildren();
		int ndestroyed = 0;
		for(int i=0;i<nchild;i++) {
			try {
				Node child = bg.getChild(i);
				if(child instanceof BranchGroup) {
					((BranchGroup)child).detach();
					destroyGeometry((BranchGroup) child);
					ndestroyed++;
				}
			} catch (Exception e) {
			}
		}
		if(ndestroyed < nchild) TextMessage("Not all children destroyed for "+bg);
		bg.detach();
		bg = null;
		System.gc();
	}


	void createScene() {

		// Create a CMS scene and attach it to the virtual universe

		BranchGroup scene = createSceneGraph();
		if(scene == null) {
			TextMessage("Unable to create Scene Graph");
			return;
		}

		Universe = new SimpleUniverse(canvasCMS);

		Universe.getViewingPlatform().setNominalViewingTransform();

		viewTransform = Universe.getViewingPlatform().getViewPlatformTransform();

		Universe.addBranchGraph(scene);

	}




	public class eventHandler implements ActionListener,
										ItemListener,
										AdjustmentListener,
										TreeSelectionListener,
										MouseListener {

		public void mouseClicked(MouseEvent e){}
		public void mouseEntered(MouseEvent e){}
		public void mouseExited(MouseEvent e){}
		public void mousePressed(MouseEvent e){
			if(e.getModifiers()==InputEvent.BUTTON1_MASK) {
				//TextMessage("Left button clicked");
			}
			if(e.getModifiers()==InputEvent.BUTTON2_MASK) {
				//TextMessage("Middle button clicked");
			}
			if(e.getModifiers()==InputEvent.BUTTON3_MASK) {
				TextMessage("Right button clicked");
				TreePath path = geometryTree.getPathForLocation(e.getX(),e.getY());
				DefaultMutableTreeNode nodeChild = (DefaultMutableTreeNode) path.getLastPathComponent();

				if(nodeChild == null) return;
	
				String dName = nodeChild.toString();

				TextMessage("Toggling appearance of --> "+dName);

				int indexChild = detectorTNs.indexOf(nodeChild);

				if(indexChild == -1) return;

				BranchGroup child = (BranchGroup) detectorBGs.elementAt(indexChild);

				int iApp = appInitials.indexOf(dName.substring(0,1));
				if(iApp < 0 || iApp >= nDets - 1) iApp = nDets-1;
				TextMessage(" appearance index "+iApp);
				
				Appearance AW = solidAppearance[iApp];

				for(int c=0;c<child.numChildren();c++) {
					Node child2 = child.getChild(c);
					if(child2 instanceof Shape3D) {
						Shape3D theShape = (Shape3D) child2;
						if(theShape.getAppearance().equals(invisibleAppearance)) {
							theShape.setAppearance(AW);
						} else {
							theShape.setAppearance(invisibleAppearance);
						}			
					}
				}
			}
			if(e.getModifiers()==(InputEvent.BUTTON1_MASK|InputEvent.BUTTON3_MASK)) {
				TextMessage("Left and right button clicked together");
			}
		}
		public void mouseReleased(MouseEvent e){}


		public void valueChanged(TreeSelectionEvent event) {

			//TextMessage(event);
			TreePath path = event.getPath();

			if(path.getPathComponent(0).toString().equals("Events")) {
				if(path.getPathCount() != 3) return;
				eventTree.setSelectionRow(0);
				int therun = Integer.valueOf(path.getPathComponent(1).toString().substring(4)).intValue();
				int theevent = Integer.valueOf(path.getPathComponent(2).toString().substring(6)).intValue();
				TextMessage("Selecting event "+theevent+" from run "+therun);
				if(dataAccess.equals("SOAP")) {
					Tag tagEvent = selectEventWS(therun,theevent);
					attachEvent(tagEvent);
				}
				return;
			}


			int count = path.getPathCount();

			if(count <= 1) return;

			DefaultMutableTreeNode nodeChild = (DefaultMutableTreeNode) path.getLastPathComponent();

			if(nodeChild == null) return;

			String dName = nodeChild.toString();

			TextMessage(count+" nodes, Click on --> "+dName);

			// Is it selected or deselected?

			boolean sel = geometryTree.isPathSelected(path);
			
			int indexChild = detectorTNs.indexOf(nodeChild);

			if(indexChild == -1) return;

			BranchGroup child = (BranchGroup) detectorBGs.elementAt(indexChild);
			Group parent = (Group) parentNodes.elementAt(indexChild);

			geometryTree.setSelectionRow(0);  // So that the user can immediately re-select this node

			if(child == null) {
				TextMessage("Child "+indexChild+" "+(String)detectorNames.elementAt(indexChild)+" is null");
				return;
			}

			if(parent == null) {
				TextMessage("Child "+indexChild+" "+(String)detectorNames.elementAt(indexChild)+" has no parent");
				return;
			}

			// Find if the child is attached to the parent
			
			String data = (String) child.getUserData();

			if(data == null) {
				TextMessage("Child has no userdata - setting attached");
				data = new String("Attached");
			}

			boolean attached = false;
			if(data.equals("Attached") ) attached = true;

			if(attached) {
				TextMessage("Detaching Volume "+dName);
				child.detach();
				child.setUserData("Detached");
			} else {
				TextMessage("Attaching Volume "+dName);
				parent.addChild(child);
				child.setUserData("Attached");
			}

		}

		public void adjustmentValueChanged(AdjustmentEvent event) {
		}





		public void itemStateChanged(ItemEvent event) {

			//TextMessage(event);
			

			AbstractButton button = (AbstractButton) event.getItem();

			String sdet = button.getText();
			boolean on = (event.getStateChange() == ItemEvent.SELECTED);

			TextMessage("itemStateChanged "+sdet);

			if(sdet.startsWith("Detector ")) {
				String detI = sdet.substring(9,10);
				int iApp = appInitials.indexOf(detI);
				if(iApp < 0 || iApp >= nDets - 1) iApp = nDets-1;
				TextMessage("Traverse tree for "+sdet+" appearance index "+iApp);
				Appearance A = wireAppearance[iApp];
				if(on) {
					A = solidAppearance[iApp];
				} 


				for(int i=0;i<detectorBGs.size();i++) {
					String dName = (String) detectorNames.elementAt(i);
					if(dName.substring(0,1).equals(detI)) {
						TextMessage("Found matching detector "+dName);
						BranchGroup bg = (BranchGroup) detectorBGs.elementAt(i);
						// Try to find Shape3D children
						for(int c=0;c<bg.numChildren();c++) {
							Node child = bg.getChild(c);
							if(child instanceof Shape3D) {
								((Shape3D)child).setAppearance(A);
							}
						}
					}
				}
			}
		}

		public void actionPerformed(ActionEvent event) {

			String command = event.getActionCommand();
			TextMessage(command);


			if(command.equals("Open Web Services")) {
				dataAccess = "SOAP";
				createEventTreeWS();
				return;
			}

			if(command.equals("Read Single Event Tag Data")) {
				createEvent("Tag");
				return;
			}
			if(command.equals("Read Single Event Full Data")) {
				createEvent("Full");
				return;
			}

			if(command.equals("Open DDD Geometry")) {
				READER = "DDD";
				geometryRead = false;
				rotationsRead = false;
				destroyGeometry(detectorBG);
				createDetectorBG();
				CMSTrans.addChild(detectorBG);
				createDetectorTree();
				createDetectorMenu();
				System.gc();
				return;
			}

			if(command.equals("Open CMSIM Geometry")) {
				READER = "CMSIM";
				geometryRead = false;
				rotationsRead = false;
				destroyGeometry(detectorBG);
				createDetectorBG();
				CMSTrans.addChild(detectorBG);
				createDetectorTree();
				createDetectorMenu();
				System.gc();
				return;
			}

			if(command.equals("Sample Shapes")) {
				if(!sampleShapes) {
					createShapes();
					sampleShapes = true;
				} else {
					destroyGeometry(detectorBG);
					createDetectorBG();
					CMSTrans.addChild(detectorBG);
					sampleShapes = false;
				}
				return;
			}

			if(command.equals("Exit")) {
				destroy();
				System.exit(0);
				return;
			}

			if(event.getSource() instanceof JRadioButton) {
				JRadioButton jr = (JRadioButton) event.getSource();
				if(jr.getText().equals("100 kBytes")) {
					networkSize = 100;
					return;
				}
				if(jr.getText().equals("1 MBytes")) {
					networkSize = 1000;
					return;
				}
				if(jr.getText().equals("10 MBytes")) {
					networkSize = 10000;
					return;
				}
				if(jr.getText().equals("100 MBytes")) {
					networkSize = 100000;
					return;
				}
				networkSize = 100;
				return;
			}

			if(event.getSource() instanceof JCheckBoxMenuItem) {
				return;
			}

			if(command.equals("Start Network Test")) {

				TextMessage("Starting Network Test ...");

				if(dataAccess.equals("SOAP")) {
					accumulateEventsWS(networkSize);
				}

				return;
			}

			if(command.equals("Side View")) {
				Transform3D t1 = new Transform3D();
				CMSTrans.getTransform(t1);
				double scale = t1.getScale();
				t1.rotZ(Math.PI/2);
				t1.setScale(scale);
				CMSTrans.setTransform(t1);
				return;
			}
			if(command.equals("End View")) {
				Transform3D t1 = new Transform3D();
				CMSTrans.getTransform(t1);
				double scale = t1.getScale();
				t1.rotY(Math.PI/2);
				t1.setScale(scale);
				CMSTrans.setTransform(t1);
				return;
			}

			if(command.equals("Left")) {
				Vector3d translation = new Vector3d();
				Transform3D t1 = new Transform3D();
				CMSTrans.getTransform(t1);
				t1.get(translation);
				translation.z -= 100.0;
				t1.setTranslation(translation);
				CMSTrans.setTransform(t1);
				return;
			}
			if(command.equals("Right")) {
				Vector3d translation = new Vector3d();
				Transform3D t1 = new Transform3D();
				CMSTrans.getTransform(t1);
				t1.get(translation);
				translation.z += 100.0;
				t1.setTranslation(translation);
				CMSTrans.setTransform(t1);
				return;
			}
			if(command.equals("Zoom In")) {
				Transform3D t1 = new Transform3D();
				CMSTrans.getTransform(t1);
				double scale = t1.getScale();
				TextMessage("Old zoom scale "+scale);
				scale *= 1.2;
				t1.setScale(scale);
				CMSTrans.setTransform(t1);
				return;
			}
			if(command.equals("Zoom Out")) {
				Transform3D t1 = new Transform3D();
				CMSTrans.getTransform(t1);
				double scale = t1.getScale();
				TextMessage("Old zoom scale "+scale);
				scale /= 1.2;
				t1.setScale(scale);
				CMSTrans.setTransform(t1);
				return;
			}


			if(command.equals("Lights On") || command.equals("Lights Off")) {
				if(lighting) {
					BLights.setText("Lights On");
					lightsBG.detach();
					lighting = false;
				} else {
					BLights.setText("Lights Off");
					CMSTrans.addChild(lightsBG);
					lighting = true;
				}
				return;
			}
			if(command.equals("Idle") || command.equals("Freeze")) {
				if(twisting) {
					BIdle.setText("Idle");
					twistBG.detach();
				} else {
					BIdle.setText("Freeze");
					CMSTrans.addChild(twistBG);
				}
				twisting = !twisting;
				return;
			}

			if(command.equals("JPEG")) {
				//canvasCMS.takeSnapshot("COJAC.jpg");
				return;
			}
			if(command.equals("Dump")) {
				if(dumpfile == null) {
					try {
						dumpfile = new PrintWriter(new FileWriter("SceneGraph.txt"));
					} catch (Exception e) {
						System.err.println("Error creating dumpfile!");
						return;
					}
				}
				Dump(objRoot);
				dumpfile.close();
				dumpfile = null;
				TextMessage("Finished Dump");
				return;
			}

			if(command.equals("Display Selected Detectors")) {

				if(detectorBG != null) {
					geometryRead = false;
					rotationsRead = false;
					destroyGeometry(detectorBG);
					createDetectorBG();
					CMSTrans.addChild(detectorBG);
					createDetectorTree();
					createDetectorMenu();
				}

				System.gc();

				TextMessage("New details");
				return;
			}

			TextMessage(" ");

		}

	}

	void Dump(Group g) {
		// Recursively dumps all the Shape3D objects in this Group as ASCII
		// text representations
		if(!g.getCapability(Group.ALLOW_CHILDREN_READ)) return;
		for(int i=0;i<g.numChildren();i++) {
			Node member = g.getChild(i);
			if(member instanceof Group) {
				Dump((Group) member);
			}
			if(member instanceof Shape3D) {
				if(member.getCapability(Shape3D.ALLOW_GEOMETRY_READ)) {
					GeometryArray geometry = (GeometryArray) ( (Shape3D) member).getGeometry();
					if(geometry.getCapability(GeometryArray.ALLOW_COUNT_READ) && 
						geometry.getCapability(GeometryArray.ALLOW_COORDINATE_READ) &&
						geometry.getCapability(GeometryArray.ALLOW_FORMAT_READ)) {
						dumpfile.println("Shape3D Type : "+g.getChild(i).getUserData().getClass());
						TextMessage("Type "+g.getChild(i).getUserData().getClass());
						dumpfile.println(" Vertices : "+geometry.getVertexCount());
						dumpfile.println(" Format   : "+geometry.getVertexFormat());
						int nvert = geometry.getVertexCount();
						double vertices [] = new double[nvert*3];
						geometry.getCoordinates(0,vertices);
						for(int j=0;j<nvert*3;) {
							dumpfile.println(vertices[j++]+" "+vertices[j++]+" "+vertices[j++]);
						}
					} else {
						TextMessage("No coordinate read for "+g.getChild(i).getUserData().getClass());
					}
				} else {
					if(g.getChild(i).getUserData() != null) {
						TextMessage("No geometry read for "+g.getChild(i).getUserData().getClass());
					} else {
						TextMessage("No geometry read for "+g.getChild(i).getClass());
					}
				}
			}
		}
	}

	void buildGUI() {

		JMenuItem menuItem;

		frameContainer = getContentPane();

	    frameContainer.setLayout(new BorderLayout());

		frameContainer.setBackground(Color.lightGray);
		
		menuBar = new JMenuBar();

		JMenu databaseMenu = new JMenu("File");

		//menuItem = databaseMenu.add(new JMenuItem("Open Web Services"));
		//menuItem.addActionListener(new eventHandler());
		//menuItem = databaseMenu.add(new JMenuItem("Read Single Event Tag Data"));
		//menuItem.addActionListener(new eventHandler());
		//menuItem = databaseMenu.add(new JMenuItem("Read Single Event Full Data"));
		//menuItem.addActionListener(new eventHandler());
		//menuItem = databaseMenu.add(new JMenuItem("Open DDD Geometry"));
		//menuItem.addActionListener(new eventHandler());
		//menuItem = databaseMenu.add(new JMenuItem("Open CMSIM Geometry"));
		//menuItem.addActionListener(new eventHandler());
		//menuItem = databaseMenu.add(new JMenuItem("Sample Shapes"));
		//menuItem.addActionListener(new eventHandler());
		databaseMenu.add(new JSeparator());
		menuItem = databaseMenu.add(new JMenuItem("Exit"));
		menuItem.addActionListener(new eventHandler());

		menuBar.add(databaseMenu);

		setJMenuBar(menuBar);

		createViewer();

		setVisible(true);

		TextMessage("GUI built and ready");

	}

	public void createDetectorBG() {
		detectorBG = new BranchGroup();
		detectorBG.setCapability(BranchGroup.ALLOW_DETACH);
		detectorBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
		detectorBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		detectorBG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
	}



	public void setupAppearances() {

		TextMessage("Setting up appearances for "+nDets+" names: "+appInitials);

		Color3f [] dC = new Color3f[nDets];
		dC[0] = new Color3f(1.0f, 0.0f, 0.0f);
		dC[1] = new Color3f(0.0f, 1.0f, 0.0f);
		dC[2] = new Color3f(0.0f, 0.0f, 1.0f);
		dC[3] = new Color3f(1.0f, 1.0f, 0.0f);
		dC[4] = new Color3f(1.0f, 0.0f, 1.0f);
		dC[5] = new Color3f(0.0f, 1.0f, 1.0f);
		dC[6] = new Color3f(0.5f, 0.5f, 0.0f);
		dC[7] = new Color3f(0.0f, 0.5f, 0.5f);
		dC[8] = new Color3f(0.2f, 0.0f, 0.8f);
		dC[9] = new Color3f(0.5f, 0.0f, 1.0f);
		dC[10]= new Color3f(1.0f, 0.5f, 0.0f);
		dC[11]= new Color3f(0.2f, 0.5f, 0.8f);
		dC[12]= new Color3f(1.0f, 0.0f, 0.5f);
		dC[13]= new Color3f(0.1f, 0.8f, 0.2f);
		dC[14]= new Color3f(0.2f, 0.2f, 0.2f);

		// Make a wireframe
		PolygonAttributes paW = new PolygonAttributes();
		paW.setPolygonMode(paW.POLYGON_LINE);
		paW.setCullFace(paW.CULL_NONE); 

		PolygonAttributes paS = new PolygonAttributes();
		paS.setPolygonMode(paS.POLYGON_FILL);
		paS.setCullFace(paS.CULL_NONE); 

		// Set up transparency	
		TransparencyAttributes ta = new TransparencyAttributes();
		ta.setTransparencyMode(TransparencyAttributes.BLENDED);
		ta.setTransparency(0.3f);

		ColoringAttributes [] coloring = new ColoringAttributes[nDets]; 
		Material [] material = new Material[nDets];

		for(int i=0;i<nDets;i++) {
			coloring[i] = new ColoringAttributes();
			coloring[i].setColor(dC[i]);

			material[i] = new Material();
			material[i].setAmbientColor(dC[i]);
			//material[i].setEmissiveColor(new Color3f(0.1f,0.1f,0.1f));
			//material[i].setEmissiveColor(dC[i]);
			material[i].setSpecularColor(new Color3f(0.5f,0.5f,0.7f));
			material[i].setDiffuseColor(dC[i]);//new Color3f(0.5f,0.5f,0.7f));
			material[i].setShininess(12.0f);
			material[i].setLightingEnable(true);

			wireAppearance[i] = new Appearance();
			wireAppearance[i].setPolygonAttributes(paW);
			wireAppearance[i].setTransparencyAttributes(ta);
			wireAppearance[i].setColoringAttributes(coloring[i]);

			solidAppearance[i] = new Appearance();
			solidAppearance[i].setPolygonAttributes(paS);
			solidAppearance[i].setMaterial(material[i]);
		}

		invisibleAppearance = new Appearance();

		RenderingAttributes ra = new RenderingAttributes();
		ra.setVisible(false);
		invisibleAppearance.setRenderingAttributes(ra);
	}

	public void readGeometry() {
		if(!READER.equals("DDD")) {
			return;
			//readG3Geometry();
		} else {
			readDDDGeometry();
		}
		geometryRead = true;
	}

	public void readRotations() {
		if(!READER.equals("DDD")) {
			return;
			//readG3Rotations();
		} else {
			readDDDRotations();
		}
		rotationsRead = true;
	}

	public class Positional {
		public String parent;
		public String child;
		public Transform3D transform;
		public boolean attached; // indicates whether attached to the scene graph
		public boolean visible; // indicates that the child volume should be visible
		public boolean leaf; // indicates a leaf node
		public int indexChild; // pointer to the child volume in the vectorShapes vector

		Positional(String _p, String _c, Transform3D _t, int _i, boolean _v, boolean _l) {
			parent = new String(_p);
			child = new String(_c);
			transform = _t;
			indexChild = _i;
			attached = false;
			visible = _v;
			leaf = _l;
		}
	}

	public void readDDDRotations() {
		DDDReader.loadRotations();
	}

	public void readDDDGeometry() {

		String [] gFile = new String[20];
		int nFile = 1;

		gFile[0] = "cms.xml";

		if(CBoxBeam.isSelected()) gFile[nFile++] = "beampipe.xml";

		if(CBoxMuonForward.isSelected()) gFile[nFile++] = "mf.xml";
		if(CBoxMuonBarrel.isSelected()) gFile[nFile++] = "mb.xml";

		if(CBoxHcal.isSelected()) gFile[nFile++] = "hcalbarrel.xml";
		if(CBoxHcal.isSelected()) gFile[nFile++] = "hcalendcap.xml";
		if(CBoxHcal.isSelected()) gFile[nFile++] = "hcal.xml";

		if(CBoxVcal.isSelected()) gFile[nFile++] = "vcal.xml";

		if(CBoxEcalBarrel.isSelected() || CBoxEcalForward.isSelected()) gFile[nFile++] = "ecal.xml";
		
		if(CBoxPixel.isSelected() || 
		   CBoxTrak.isSelected() ||
		   CBoxSiVertex.isSelected()) gFile[nFile++] = "trak.xml";

		if(CBoxPixel.isSelected()) gFile[nFile++] = "pxlx.xml";

		//if(CBoxTrak.isSelected() || 
		//   CBoxSiVertex.isSelected()) gFile[nFile++] = "tec.xml";

		if(CBoxTrak.isSelected()) gFile[nFile++] = "tob.xml";
		if(CBoxTrak.isSelected()) gFile[nFile++] = "tib.xml";

		for(int ifile=0;ifile<nFile;ifile++) {
			TextMessage("File "+gFile[ifile]);
		}

		rotM = DDDReader.getRotMatrices();

		Vector vectorShapes = new Vector(100);

		vectorPositionals = new Vector(100);

		nVolumes = 0;

		for(int ifile=0;ifile<nFile;ifile++) {

			DDDReader.open(gFile[ifile]);

			int nvols = DDDReader.getNumVolumes();

			TextMessage("There are "+nvols+" Volumes in the file");

			int nparts = DDDReader.getNumPosParts();

			TextMessage("There are "+nparts+" PosParts in the file");

			for(int ivol=0;ivol<nvols;ivol++) {
		
				String nodeName = DDDReader.getVolumeName(ivol);
				String shapeName = DDDReader.getVolumeShape(ivol);

				TextMessage("Volume "+nodeName+" Shape "+shapeName);

				float [] params = DDDReader.getVolumeParams(ivol);

				if(detectorInitials.indexOf(nodeName.substring(0,1)) == -1) {
					detectorInitials += nodeName.substring(0,1);
				}

				int idetApp = appInitials.indexOf(nodeName.substring(0,1));
				if(idetApp < 0 || idetApp >= (nDets-1)) idetApp = nDets-1;

				Appearance app = invisibleAppearance;

				Shape3D volShape = createVolume(shapeName,params,app);

				vectorShapes.addElement(volShape);
			}

			for(int ipart=0;ipart<nparts;ipart++) {

				String parent = DDDReader.getPosParent(ipart);
				String child = DDDReader.getPosChild(ipart);

				// Find the index of the child volume
				int indexChild = -1;
				for(int ivol=0;ivol<nvols;ivol++) {
					String volName = DDDReader.getVolumeName(ivol);
					if(volName.equals(child)) {
						indexChild = ivol + nVolumes;
						break;
					}
				}

				if(indexChild == -1) {
					System.err.println("Cannot find child "+child);
					System.exit(0);
				}

				Transform3D t3d = new Transform3D();

				if(DDDReader.getPosTranslationPresent(ipart)) {
					t3d.setTranslation(DDDReader.getPosTranslation(ipart));
				}

				if(DDDReader.getPosRotationPresent(ipart)) {
					int irot = DDDReader.getPosRotation(ipart);
					t3d.setRotation(rotM[irot]);
				}
					

				boolean visible = false;

				// Include the top level envelope if nothing is selected

				if(child.equals("CMSE") &&
				   !CBoxMuonForward.isSelected() &&
				   !CBoxMuonBarrel.isSelected() &&
				   !CBoxPixel.isSelected() &&
				   !CBoxSiVertex.isSelected() &&
				   !CBoxTrak.isSelected() &&
				   !CBoxBeam.isSelected() &&
				   !CBoxVcal.isSelected() &&
				   !CBoxHcal.isSelected() &&
				   !CBoxEcalForward.isSelected() &&
				   !CBoxEcalBarrel.isSelected()) visible = true;

				if(CBoxMuonForward.isSelected()) {
					if(child.equals("MW1A")) visible = true;
					if(child.equals("MW21")) visible = true;
					if(child.equals("MW31")) visible = true;
					if(child.equals("MW41")) visible = true;
					if(child.equals("MW11")) visible = true;
					if(child.equals("MW22")) visible = true;
					if(child.equals("MW32")) visible = true;
					if(child.equals("MW42")) visible = true;
					if(child.equals("MW12")) visible = true;
					if(child.equals("MW13")) visible = true;
				}

				if(CBoxMuonBarrel.isSelected()) {
					if(child.startsWith("RB")) visible = true; // Barrel absorber
					if(child.equals("MYBL")) visible = true; // Barrel absorber
					if(child.startsWith("RC") && child.endsWith("X")) visible = true; // Barrel RPCs
					if(child.equals("RP1I")) visible = true; // Barrel RPCs
					if(child.equals("RP1O")) visible = true; // Barrel RPCs
					if(child.equals("RP2I")) visible = true; // Barrel RPCs
					if(child.equals("RP2O")) visible = true; // Barrel RPCs
					if(child.equals("RP3S")) visible = true; // Barrel RPCs
					if(child.equals("RP4A")) visible = true; // Barrel RPCs
					if(child.equals("RP4B")) visible = true; // Barrel RPCs
					if(child.equals("RP4C")) visible = true; // Barrel RPCs
					if(child.equals("RP4D")) visible = true; // Barrel RPCs
				}

				if(CBoxPixel.isSelected()) {
					//if(child.startsWith("PX")) visible = true;
					if(child.equals("PXB1")) visible = true; // barrel pixels
					if(child.equals("PXB2")) visible = true; // barrel pixels
					if(child.equals("PXB3")) visible = true; // barrel pixels
	
					if(child.equals("PXFD")) visible = true; // forward pixels
				}

				if(CBoxSiVertex.isSelected()) {
					if(child.startsWith("SF")) visible = true;
					if(child.startsWith("SB")) visible = true;

					if(child.startsWith("GF")) visible = true;
					if(child.startsWith("GB")) visible = true;

					if(child.startsWith("SP")) visible = true;
					if(child.startsWith("SVC")) visible = true;	

					if(child.startsWith("TS")) visible = true;
					if(child.startsWith("TG")) visible = true;
				}

				if(CBoxTrak.isSelected()) {
					if(child.startsWith("GWX")) visible = true;
					if(child.startsWith("SWX")) visible = true;
					if(child.startsWith("SWY")) visible = true;
				}

				if(CBoxBeam.isSelected()) {
					if(child.startsWith("Beam")) visible = true;
				}

				if(CBoxVcal.isSelected()) {
					if(child.startsWith("HV")) visible = true;
				}

				if(CBoxHcal.isSelected()) {
					if(child.equals("HCSF")) visible = true;
					if(child.equals("HEMO")) visible = true;
					if(child.equals("HTS1")) visible = true;
				}

				if(CBoxEcalForward.isSelected()) {	
					if(child.equals("EE01")) visible = true; // EE01...EE11
					if(child.equals("EO01")) visible = true; // EO01...EO11
					if(child.equals("EE11")) visible = true; // EE01...EE11
					if(child.equals("EO11")) visible = true; // EO01...EO11
				}

				if(CBoxEcalBarrel.isSelected()) {

					if(child.equals("E1AL")) visible = true;
					if(child.equals("E2AL")) visible = true;
					if(child.equals("E3AL")) visible = true;
					if(child.equals("E4AL")) visible = true;
					if(child.equals("E5AL")) visible = true;
					if(child.equals("E6AL")) visible = true;
					if(child.equals("E7AL")) visible = true;
					if(child.equals("E8AL")) visible = true;
					if(child.equals("EJAL")) visible = true;
					if(child.equals("EKAL")) visible = true;
					if(child.equals("ELAL")) visible = true;
					if(child.equals("EMAL")) visible = true;
					if(child.equals("ENAL")) visible = true;
					if(child.equals("EOAL")) visible = true;
					if(child.equals("EPAL")) visible = true;
					if(child.equals("EQAL")) visible = true;

				}

				Positional p = new Positional(parent,child,t3d,indexChild,visible,visible);

				vectorPositionals.addElement(p);

			}

			// Clear the variables allocated in DDDReader for this file

			DDDReader.clear();

			nVolumes += nvols;
		
		}

		// Now we prune the tree to remove unwanted (invisible) detectors

		TextMessage("Pruning unwanted detectors ");


		// First go upwards in the tree from all the leaf nodes,
		// marking parents as visible

		int numInvisible;
		int lastNumInvisible = vectorPositionals.size();
		do {
			numInvisible = 0;

			for(int i=0;i<vectorPositionals.size();i++) {
				Positional p = (Positional) vectorPositionals.elementAt(i);
				if(!p.visible) {
					numInvisible++;
					continue;
				}
				// Find the positionals that have p's parent as a child
				for(int j=0;j<vectorPositionals.size();j++) {
					Positional q = (Positional) vectorPositionals.elementAt(j);
					if(!q.child.equals(p.parent)) continue;
					q.visible = true;
				}
			}

			System.out.print(".");

			if(numInvisible <= 0) break;
			if(lastNumInvisible == numInvisible) break;

			lastNumInvisible = numInvisible;

		} while(true);

		// Now go downwards in the tree from each leaf node and remove anything
		// hanging below the leaves

		int numNulled;
		do {

			numNulled = 0;
			for(int i=0;i<vectorPositionals.size();i++) {
				Positional p = (Positional) vectorPositionals.elementAt(i);
				if(p.leaf || p.parent == null) {
					// Find the positionals that have p's child as a parent
					for(int j=0;j<vectorPositionals.size();j++) {
						Positional q = (Positional) vectorPositionals.elementAt(j);
						if(q.parent == null) continue;
						if(!q.parent.equals(p.child)) continue;
						q.parent = null;
						numNulled++;
					}
				}
			}

			TextMessage("("+numNulled+")");

		} while(numNulled > 0);


		TextMessage("Prune complete");

		// Physically remove the invisible Positionals from the list
		int iv = 0;
		while(iv < vectorPositionals.size()) {
			Positional p = (Positional) vectorPositionals.elementAt(iv);
			if(p.visible && p.parent != null) {
				if(p.leaf) TextMessage("Leaf "+p.parent+"->"+p.child);
				iv++;
			} else {
				//TextMessage("Removing "+p.child);
				vectorPositionals.remove(iv);
			}
		}

		volShapes = new Shape3D[nVolumes];

		for(int i=0;i<nVolumes;i++) {
			volShapes[i] = new Shape3D();
			volShapes[i] = (Shape3D) vectorShapes.elementAt(i);
		}

		vectorShapes = null;

		TextMessage("Finished reading all volume parameters");

	}

/*
	public void readG3Geometry() {

		geantReader.open(cmsimFile);

		nVolumes = geantReader.getNumVolumes();
		
		int nlines = 0;
		int count = 0;

		int nRotMatrices = geantReader.getNumRotations();

		TextMessage("There are "+nRotMatrices+" Rotation Matrices in the file");

		rotM = new Matrix3d[nRotMatrices];

		for(int irot=0;irot<nRotMatrices;irot++) {

			float [] params = new float[100];

			int nparams = geantReader.getRotation(irot,params);

			if(nparams < 6) {
				TextMessage("Insufficient parameters for Rotation matrix "+irot);
				continue;
			}

			double thetaX,phiX, thetaY,phiY, thetaZ,phiZ;

			thetaX = (double) deg2rad * (double) params[0];
			phiX   = (double) deg2rad * (double) params[1];

			thetaY = (double) deg2rad * (double) params[2];
			phiY   = (double) deg2rad * (double) params[3];

			thetaZ = (double) deg2rad * (double) params[4];
			phiZ   = (double) deg2rad * (double) params[5];

			rotM[irot] = new Matrix3d();
			rotM[irot].setIdentity();

			rotM[irot].m00 = Math.cos(phiX) * Math.sin(thetaX);
			rotM[irot].m10 = Math.sin(phiX) * Math.sin(thetaX);
			rotM[irot].m20 = Math.cos(thetaX);

			rotM[irot].m01 = Math.cos(phiY) * Math.sin(thetaY);
			rotM[irot].m11 = Math.sin(phiY) * Math.sin(thetaY);
			rotM[irot].m21 = Math.cos(thetaY);

			rotM[irot].m02 = Math.cos(phiZ) * Math.sin(thetaZ);
			rotM[irot].m12 = Math.sin(phiZ) * Math.sin(thetaZ);
			rotM[irot].m22 = Math.cos(thetaZ);

		}

		TextMessage("Created "+geantReader.getNumRotations()+" rotation matrices");

		volShapes = new Shape3D[nVolumes];

		TextMessage("There are "+nVolumes+" Volumes in the file");

		int nNull = 0;

		String nullVolumes = "";

		for(int ivol=0;ivol<nVolumes;ivol++) {
		
			String nodeName = geantReader.getVolumeName(ivol);
			String shapeName = geantReader.getShapeName(ivol);

			float [] params = new float[100];
			int nparams = geantReader.getShapeParams(ivol,params);

			if(detectorInitials.indexOf(nodeName.substring(0,1)) == -1) {
				detectorInitials += nodeName.substring(0,1);
			}

			int idetApp = appInitials.indexOf(nodeName.substring(0,1));
			if(idetApp < 0 || idetApp >= (nDets-1)) idetApp = nDets-1;

			if(nparams <= 0) {
				nNull++;
				nullVolumes += nodeName+" ";
				shapeName = "BRIK";
				params[0] = 0.0f;
				params[1] = 0.0f;
				params[2] = 0.0f;
				//TextMessage("Volume "+nodeName+" shape "+shapeName+" Colour "+idetApp+" with "+nparams+" params");
				//continue;
			}

			//TextMessage("Treating Volume "+nodeName+" shape "+shapeName+" Colour "+idetApp+" with "+nparams+" params");

			Appearance app = invisibleAppearance;

			volShapes[ivol] = createVolume(shapeName,params,app);
			
		}

		if(nNull > 0) TextMessage(nNull+" volumes with zero parameters: "+nullVolumes);

		TextMessage("Finished reading all volume parameters");

	}

*/

	Shape3D createVolume(String shapeName, float [] params, Appearance app) {
		// Creates volume according to the shape name and associated parameters and Appearance

		if(shapeName.equals("TUBE") || shapeName.equals("TUBS")) {
			float frmin = params[0];
			float frmax = params[1];
			float fdz = params[2];

			float fphi1 = 0.0f;
			float fphi2 = 360.0f;

			if(shapeName.equals("TUBS")) {
				fphi1 = params[3];
				float fphiEnd = params[4];
				if(fphiEnd < fphi1) fphiEnd += 360.0f;
				fphi2 = fphiEnd - fphi1;
			}

			float [] rmin = new float[2];
			float [] rmax = new float[2];
			float [] z = new float[2];

			z[0] = -fdz;
			z[1] = fdz;

			rmin[0] = frmin;
			rmin[1] = frmin;
			rmax[0] = frmax;
			rmax[1] = frmax;

			int nsections = 2;
			int nfaces = 20;

			polyCone coneObj = new polyCone(nfaces, deg2rad*fphi1, deg2rad*fphi2, nsections, rmin, rmax, z, app);
       		Shape3D cone = coneObj.getChild();
			cone.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
			cone.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
			cone.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
			//cone.setUserData(shapeClass);

			return cone;

		}

		if(shapeName.equals("CONE")) {

			float [] rmin = new float[2];
			float [] rmax = new float[2];
			float [] z = new float[2];

			z[0] = params[0];
			z[1] = -z[0];

			rmin[0] = params[1];
			rmax[0] = params[2];
			rmin[1] = params[3];
			rmax[1] = params[4];

			float fphi1 = 0.0f;
			float fphi2 = 360.0f;

			int nsections = 2;
			int nfaces = 20;

			polyCone coneObj = new polyCone(nfaces, deg2rad*fphi1, deg2rad*fphi2, nsections, rmin, rmax, z, app);
       		Shape3D cone = coneObj.getChild();
			cone.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
			cone.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
			cone.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
			//cone.setUserData(shapeClass);

			return cone;
		}

		if(shapeName.equals("CONS")) {

			float [] rmin = new float[2];
			float [] rmax = new float[2];
			float [] z = new float[2];

			z[0] = params[0];
			z[1] = -z[0];

			rmin[0] = params[1];
			rmax[0] = params[2];
			rmin[1] = params[3];
			rmax[1] = params[4];

			float fphi1 = params[5];
			float fphi2 = params[6];

			int nsections = 2;
			int nfaces = 20;

			polyCone coneObj = new polyCone(nfaces, deg2rad*fphi1, deg2rad*fphi2, nsections, rmin, rmax, z, app);
       		Shape3D cone = coneObj.getChild();
			cone.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
			cone.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
			cone.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
			//cone.setUserData(shapeClass);

			return cone;
		}

		if(shapeName.equals("PCON") || shapeName.equals("PGON")) {

			int nfaces = 20; // Use this as a default for number of section sides

			float fphi = params[0];
			float fdphi = params[1];
			int nsections = (int) params[2];
			int ips = 3;
			if(shapeName.equals("PGON")) {
				nfaces = (int) params[2];
				nsections = (int) params[3];
				ips = 4;
			}

			float [] rmin = new float[nsections];
			float [] rmax = new float[nsections];
			float [] z = new float[nsections];

			for(int i=0;i<nsections;i++) {
				z[i] = params[ips];
				rmin[i] = params[ips+1];
				rmax[i] = params[ips+2];
				ips += 3;
				//TextMessage(rmin[isection]+" "+rmax[isection]+" "+z[isection]);
			}

			polyCone coneObj = new polyCone(nfaces, deg2rad*fphi, deg2rad*fdphi, nsections, rmin, rmax, z, app);
       		Shape3D cone = coneObj.getChild();
			cone.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
			cone.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
			cone.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
			//cone.setUserData(shapeClass);

			return cone;
		}

		if(shapeName.equals("BRIK")) {
			float fdx = params[0];
			float fdy = params[1];
			float fdz = params[2];

	        trapezoid BrickObj = new trapezoid(fdx, fdx, fdy, fdz, app);
       		Shape3D theBrick = BrickObj.getChild();
			theBrick.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
			theBrick.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
			theBrick.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
			//theBrick.setUserData(shapeClass);

			return theBrick;
		}

		if(shapeName.equals("TRAP")) {
			float fdz = params[0];
			float ftheta = params[1];
			float fphi = params[2];
			float fh1 = params[3];
			float fbl1 = params[4];
			float ftl1 = params[5];
			float falpha1 = params[6];
			float fh2 = params[7];
			float fbl2 = params[8];
			float ftl2 = params[9];
			float falpha2 = params[10];

	        gentrapezoid trap = new gentrapezoid(fdz, deg2rad*ftheta, deg2rad*fphi, fh1, fbl1, ftl1,
												deg2rad*falpha1, fh2, fbl2, ftl2, deg2rad*falpha2, app);
       		Shape3D thetrap = trap.getChild();
			thetrap.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
			thetrap.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
			thetrap.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
			//thetrap.setUserData(shapeClass);

			return thetrap;
		}

		if(shapeName.equals("TRD1")) {
			float fdx1 = params[0];
			float fdx2 = params[1];
			float fdy = params[2];
			float fdz = params[3];

	        trapezoid trap = new trapezoid(fdx1, fdx2, fdy, fdz, app);
      		Shape3D thetrap = trap.getChild();
			thetrap.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
			thetrap.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
			thetrap.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
			//thetrap.setUserData(shapeClass);

			return thetrap;
		}

		TextMessage("\nUNTREATED SHAPE CLASS "+shapeName);
		System.exit(0);

		return null;
	}


	public void createShapes() {

		// Build the tree

		BranchGroup currentBG = detectorBG;
		currentBG.setUserData("Attached");

		for(int i=0;i<8;i++) {

			BranchGroup newBG = new BranchGroup();
			newBG.setCapability(BranchGroup.ALLOW_DETACH);
			newBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
			newBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
			newBG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
			newBG.setUserData("Attached");

			// Create the TransformGroup that this new BG will hang from

			TransformGroup newTG = new TransformGroup();

			Transform3D t3d = new Transform3D();

			// first the translation

			Vector3d t = new Vector3d(0,0,-1000+i*300);
			t3d.setTranslation(t);

			newTG.setTransform(t3d);

			newTG.addChild(newBG);

			BranchGroup newBG2 = new BranchGroup();
			newBG2.setCapability(BranchGroup.ALLOW_DETACH);
			newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
			newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
			newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
			newBG2.setUserData("Attached");

			newBG2.addChild(newTG);

			Appearance app = wireAppearance[0];
			if(i%2 == 1) app = solidAppearance[0];

			Shape3D thisShape = null;

			if(i < 2) {
				float [] rmin = new float[2];
				float [] rmax = new float[2];
				float [] z = new float[2];

				z[0] = 0.f;
				z[1] = 100.f;

				rmin[0] = 50.f;
				rmax[0] = 200.f;
				rmin[1] = 50.f;
				rmax[1] = 200.f;

				float fphi1 = 0.0f;
				float fphi2 = 360.0f;

				int nsections = 2;
				int nfaces = 16;

				polyCone coneObj = new polyCone(nfaces, deg2rad*fphi1, deg2rad*fphi2, nsections, rmin, rmax, z, app);
			    thisShape = coneObj.getChild();
				thisShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
				thisShape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
				thisShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);

			} else if(i < 4) {

					float fdx1 = 0;
					float fdx2 = 100;
					float fdy = 50;
					float fdz = 50;

			        trapezoid trap = new trapezoid(fdx1, fdx2, fdy, fdz, app);
		       		thisShape = trap.getChild();
					thisShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
					thisShape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
					thisShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);

			} else if(i < 6) {

					float fdx = 100;
					float fdy = 50;
					float fdz = 50;

			        trapezoid BrickObj = new trapezoid(fdx, fdx, fdy, fdz, app);
		       		thisShape = BrickObj.getChild();
					thisShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
					thisShape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
					thisShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);

			} else if(i < 8) {
					float fdz = 50;
					float ftheta = 10;
					float fphi = 30;
					float fh1 = 50;
					float fbl1 = 50;
					float ftl1 = 50;
					float falpha1 = 20;
					float fh2 = 100;
					float fbl2 = 100;
					float ftl2 = 100;
					float falpha2 = 30;

			        gentrapezoid trap = new gentrapezoid(fdz, deg2rad*ftheta, deg2rad*fphi, fh1, fbl1, ftl1,
														deg2rad*falpha1, fh2, fbl2, ftl2, deg2rad*falpha2, app);
		       		thisShape = trap.getChild();
					thisShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
					thisShape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
					thisShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
			}

			newBG.addChild(thisShape);

			currentBG.addChild(newBG2);

		}

	}

	public void createDetectorTree() {

		String currentDetector = "NULL";

		// Call readGeometry if not already done ...

		if(!rotationsRead) readRotations();
		if(!geometryRead) readGeometry();

		// Build the tree

		BranchGroup currentBG = detectorBG;
		currentBG.setUserData("Attached");

		DefaultMutableTreeNode detectorTree = new DefaultMutableTreeNode("Detectors");
		DefaultMutableTreeNode currentTN = detectorTree;

		detectorBGs = new Vector(1000);
		detectorTNs = new Vector(1000);
		detectorNames = new Vector(1000);
		parentNodes = new Vector(1000);

		int currentVol = 0;
		int currentLevel = 0;
		
		if(vectorPositionals.size() <= 0) {
			TextMessage("No volumes to render!");
			return;
		}

		Positional p = (Positional) vectorPositionals.elementAt(currentVol);
		currentDetector = p.parent;
		
		// Create a BranchGroup for the top level detector

		BranchGroup newBG = new BranchGroup();
		newBG.setCapability(BranchGroup.ALLOW_DETACH);
		newBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
		newBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		newBG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
		newBG.setUserData("Attached");

		currentBG.addChild(newBG);

		DefaultMutableTreeNode newTN = new DefaultMutableTreeNode(currentDetector);

		detectorBGs.addElement(newBG);
		detectorTNs.addElement(newTN);
		parentNodes.addElement(currentBG);
		detectorNames.addElement(currentDetector);

		currentTN.add(newTN);

		currentTN = newTN;

		nDetector = 0;

		String prefix = "> ";

		int level = currentLevel;

		maxLevelSeen = 0;

		if(!READER.equals("DDD")) {
			return;
			//traverseNodes(currentVol, currentBG, currentTN, level, "-"+prefix, "", detailLevel);
		} else {
			processNodes();
		}

		nDetector++;
			
		geometryTree = new JTree(detectorTree);
		geometryTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		geometryTree.putClientProperty("JTree.lineStyle", "Angled");
		treeRenderer renderer = new treeRenderer();
		geometryTree.setCellRenderer(renderer);
		geometryTree.addTreeSelectionListener(new eventHandler());
		geometryTree.addMouseListener(new eventHandler());

		scrollPaneGeometry.getViewport().add(geometryTree);

		PGeometryTree.add(scrollPaneGeometry,"Center");

		PGeometryTree.setVisible(true);

		TextMessage("Detector Initials: "+detectorInitials);

		TextMessage("Maximum detector level: "+detailLevel+" Detectors below? "+(maxLevelSeen==detailLevel));
	}

	public void processNodes() {

		// Called to process the PosPart information from the XML detector description
		//

		String thisName;

		int nAttached = 0;
		int cycle = 0;

		int nPosParts = vectorPositionals.size();
		
		do {

			nAttached = 0;

			for(int ipart=0;ipart<vectorPositionals.size();ipart++) {

				Positional p = (Positional) vectorPositionals.elementAt(ipart);

				if(p.attached) {
					nAttached++;
					continue;
				}

				//TextMessage("Processing "+p.parent+" -> "+p.child+" nDets="+detectorNames.size()+" Transform "+p.transform);

				if(p.indexChild == -1) {
					System.err.println("Cannot find child "+p.child);
					continue;
				}

				// Find all instances of the parent in the tree, and add the child

				int ninstances = 0;
				int nnodes = detectorNames.size();

				for(int inode=0;inode<nnodes;inode++) {

					String detName = (String)detectorNames.elementAt(inode);

					if(detName.equals(p.parent)) {

						ninstances++;

						BranchGroup currentBG = (BranchGroup) detectorBGs.elementAt(inode);
						DefaultMutableTreeNode currentTN = (DefaultMutableTreeNode) detectorTNs.elementAt(inode);
			
						// The hierarchy is:
						// currentBG 
						//   newBG2
						//      newTG
						//        newBG 
						//  	   Detector
						//    
		
						BranchGroup newBG2 = new BranchGroup();
						newBG2.setCapability(BranchGroup.ALLOW_DETACH);
						newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
						newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
						newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
				
						// Create the TransformGroup that this detector and other sub detectors will hang from

						TransformGroup newTG = new TransformGroup(p.transform);
						newTG.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
						newTG.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
						newTG.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);

						newBG2.addChild(newTG);

						// We need to create a new BranchGroup for the shapes hanging off this TG
						BranchGroup newBG = new BranchGroup();
						newBG.setCapability(BranchGroup.ALLOW_DETACH);
						newBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
						newBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
						newBG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);

						newBG.setUserData("Attached");

						newTG.addChild(newBG);

						Shape3D thisShape = volShapes[p.indexChild];

						try {
							if(thisShape != null) {
								newBG.addChild(thisShape);
							} else {
								TextMessage("Shape is NULL!");
							}
						} catch (Exception e) {
							// indicates the shape already has a parent: we need to
							// clone it and add the clone
							// TextMessage("Cloning "+thisName+" "+e);
					        Shape3D copyShape = new Shape3D(thisShape.getGeometry(), thisShape.getAppearance());
							copyShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);	
							copyShape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
							copyShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
							//copyShape.setUserData(thisShape.getUserData());
							newBG.addChild(copyShape);
							thisShape = copyShape;
						}

						int idetApp = appInitials.indexOf(p.child.substring(0,1));
						if(idetApp < 0 || idetApp >= (nDets-1)) idetApp = nDets-1;

						Appearance app = solidAppearance[idetApp]; 

						if(p.child.equals("CMSE")) app = wireAppearance[idetApp];

						boolean visible = false;

						if(p.leaf) thisShape.setAppearance(app);

						currentBG.addChild(newBG2);	

						DefaultMutableTreeNode thisTN = new DefaultMutableTreeNode(p.child);
						detectorTNs.addElement(thisTN);
						currentTN.add(thisTN);

						detectorBGs.addElement(newBG);
						detectorNames.addElement(p.child);
						parentNodes.addElement(newTG);
					}

				}

				if(ninstances == 0) {
					System.out.print(".");
				} else {
					p.attached = true;
					visibleDetectors += ","+p.child;
					nAttached++;
				}
			}

			TextMessage("\n\nCycle "+(++cycle)+" with nAttached="+nAttached+" of "+nPosParts);

		} while(nAttached < nPosParts);
	}





/*
	public void traverseNodes(int volume, BranchGroup currentBG, DefaultMutableTreeNode currentTN, int level, String prefix, String parentage, int maxlevel) {
		int ivol, ndivisions;
		float start=0,step=0;
		String thisName;
		boolean prune;
		double xoff=0,yoff=0,zoff=0;

		level++;

		int nNodes = geantReader.getNumSubNodes(volume);

		// nNodes = 0  No subvolumes
		// nNodes > 0  nNodes Regular children
		// nNodes < 0  volume must be divided into -nNodes divisions

		if(nNodes == 0) {
			level--;
			return;
		}

		boolean divide = false;

		if(nNodes < 0) {
			divide = true;
			prune = false;
			nNodes = -nNodes;
		}

		// The hierarchy is:
		// currentBG 
		//   loop detectors {
		//     newBG2
		//        newTG
		//          newBG -> currentBG at next level
		//   }
		//    
		
		String volName = geantReader.getVolumeName(volume);

		int []	 volumesFortran = new int[nNodes];
		int []	 rotations = new int[nNodes];
		float [] t1 = new float[nNodes];
		float [] t2 = new float[nNodes];
		float [] t3 = new float[nNodes];

		int iret = geantReader.getSubNodes(volume,volumesFortran,t1,t2,t3,rotations);

		if(Math.abs(iret) != nNodes) {
			System.err.println("Error reading sub nodes at Volume="+volume);
			System.exit(0);
		}

		thisName = volName;
		ivol = volume;
		prune = false;

		if(divide) {
			ivol = volumesFortran[0]-1;
			thisName = geantReader.getVolumeName(ivol);
			ndivisions = (int) t1[0];
			start = t2[0];
			step = t3[0];
			int axis = rotations[0];
			if(axis != 2) {
				System.err.println("Error with Divided volume "+volName+" Axis "+axis+" not supported");
				level--;
				return;
			}
			TextMessage("Dividing volume "+volName+" to "+ndivisions+" copies of "+thisName+" subnodes "+geantReader.getNumSubNodes(ivol));
		}

		for(int in=0;in<nNodes;in++) {

			Transform3D t3d;

			if(!divide) {

				ivol = volumesFortran[in]-1; // Remember the index comes from Fortran ...

				thisName = geantReader.getVolumeName(ivol);

				boolean process = false;
				prune = false;

				if(level == 2) {
					if(CBoxEcalForward.isSelected() && thisName.equals("CALO")) process = true;
					if(CBoxEcalBarrel.isSelected() && thisName.equals("CALO")) process = true;
					if(CBoxHcal.isSelected() && thisName.equals("CALO")) process = true;
					if(CBoxTrak.isSelected() && thisName.equals("TRAK")) process = true;
					if(CBoxPixel.isSelected() && thisName.equals("TRAK")) process = true;
					if(CBoxSiVertex.isSelected() && thisName.equals("TRAK")) process = true;
					if(CBoxBeam.isSelected() && thisName.equals("BEAM")) process = true;
					if(CBoxMuonForward.isSelected() && thisName.equals("MUON")) process = true;
					if(CBoxMuonBarrel.isSelected() && thisName.equals("MUON")) process = true;
					if(CBoxVcal.isSelected() && thisName.equals("VCAL")) process = true;
				
					if(!process) continue;

				} else if(level == 3) {

					process = true;

					if(thisName.startsWith("H") && !CBoxHcal.isSelected()) process = false;
					if(thisName.startsWith("H") && CBoxVcal.isSelected()) process = true;

					if(thisName.equals("PXLX") && !CBoxPixel.isSelected()) process = false;
					if(thisName.equals("SVTX") && !CBoxSiVertex.isSelected()) process = false;
			
					if(!process) continue;

				}

				if(parentage.startsWith("-CMSE-CALO-ECAL-EREG-EHB")) continue;
				if(parentage.startsWith("-CMSE-CALO-ECAL-EREG-SF")) continue;	

				if(parentage.startsWith("-CMSE-CALO-ECAL") && !CBoxEcalForward.isSelected() && !CBoxEcalBarrel.isSelected() ) continue;

				if(parentage.startsWith("-CMSE-CALO-ECAL-EREG-ENCA") && !CBoxEcalForward.isSelected()) continue;
				if(parentage.startsWith("-CMSE-CALO-ECAL-EREG-ESPM") && !CBoxEcalBarrel.isSelected()) continue;

				if(parentage.startsWith("-CMSE-TRAK-PXLX") && !CBoxPixel.isSelected()) continue;
				if(parentage.startsWith("-CMSE-TRAK-PXLX-PBXS")) continue;
				if(parentage.startsWith("-CMSE-TRAK-PXLX-PXSF")) continue;

				if(parentage.startsWith("-CMSE-MUON-MB") && !CBoxMuonBarrel.isSelected()) continue;
				if(parentage.startsWith("-CMSE-MUON-MF") && !CBoxMuonForward.isSelected()) continue;

				if(CBoxTrak.isSelected()) {
				//if(thisName.startsWith("BT")) prune = true;
				}

				if(CBoxPixel.isSelected()) {
					//if(thisName.startsWith("PX")) prune = true;
					if(thisName.equals("PXB1")) prune = true; // barrel pixels
					if(thisName.equals("PXB2")) prune = true; // barrel pixels
					if(thisName.equals("PXB3")) prune = true; // barrel pixels
	
					if(thisName.equals("PXFD")) prune = true; // forward pixels
				}

				if(CBoxSiVertex.isSelected()) {
					if(thisName.startsWith("SF")) prune = true;
					if(thisName.startsWith("SB")) prune = true;

					if(thisName.startsWith("GF")) prune = true;
					if(thisName.startsWith("GB")) prune = true;

					if(thisName.startsWith("SP")) prune = true;
					if(thisName.startsWith("SVC")) prune = true;	

					if(thisName.startsWith("TS")) prune = true;
					if(thisName.startsWith("TG")) prune = true;
				}

				if(CBoxBeam.isSelected()) {
					if(thisName.startsWith("Beam")) prune = true;
				}

				if(CBoxVcal.isSelected()) {
					if(thisName.startsWith("HV")) prune = true;
				}


				if(CBoxMuonBarrel.isSelected()) {
					if(thisName.startsWith("RB")) prune = true; // Barrel absorber
					if(thisName.equals("MYBL")) prune = true; // Barrel absorber

					if(thisName.startsWith("RC") && thisName.endsWith("X")) prune = true; // Barrel RPCs
					if(thisName.equals("RP1I")) prune = true; // Barrel RPCs
					if(thisName.equals("RP1O")) prune = true; // Barrel RPCs
					if(thisName.equals("RP2I")) prune = true; // Barrel RPCs
					if(thisName.equals("RP2O")) prune = true; // Barrel RPCs
					if(thisName.equals("RP3S")) prune = true; // Barrel RPCs
					if(thisName.equals("RP4A")) prune = true; // Barrel RPCs
					if(thisName.equals("RP4B")) prune = true; // Barrel RPCs
					if(thisName.equals("RP4C")) prune = true; // Barrel RPCs
					if(thisName.equals("RP4D")) prune = true; // Barrel RPCs

				}

				if(CBoxMuonForward.isSelected()) {
					if(thisName.equals("MW1A")) prune = true;
					if(thisName.equals("MW21")) prune = true;
					if(thisName.equals("MW31")) prune = true;
					if(thisName.equals("MW41")) prune = true;
					if(thisName.equals("MW11")) prune = true;
					if(thisName.equals("MW22")) prune = true;
					if(thisName.equals("MW32")) prune = true;
					if(thisName.equals("MW42")) prune = true;
					if(thisName.equals("MW12")) prune = true;
					if(thisName.equals("MW13")) prune = true;
				}

				if(CBoxHcal.isSelected()) {
					if(thisName.equals("HCSF")) prune = true;
					if(thisName.equals("HEMO")) prune = true;
					if(thisName.equals("HTS1")) prune = true;
				}

				if(CBoxEcalForward.isSelected()) {	

					if(thisName.startsWith("EE")) prune = true; // EE01...EE11
					if(thisName.startsWith("EO")) prune = true; // EO01...EO11

				}

				if(CBoxEcalBarrel.isSelected()) {

					if(level > 3 && thisName.endsWith("AL")) prune = true; // E1AL...EQAL

				}

				visibleDetectors += ","+thisName;


				nDetector++;
	
				xoff = (double) t1[in];
				yoff = (double) t2[in];
				zoff = (double) t3[in];

				int irot = rotations[in] - 1; // rotations[in] is the Fortran-based index. irot = -1 is thus the null rotation

				t3d = new Transform3D();

				// first the translation

				Vector3d t = new Vector3d(xoff,yoff,zoff);
				t3d.setTranslation(t);

				// then the rotation

				if(irot >= 0) {
					t3d.setRotation(rotM[irot]);
					//TextMessage("\nrotM for "+thisName+"\n"+
					//	rotM[irot].m00+" "+rotM[irot].m01+" "+rotM[irot].m02+"\n"+
					//	rotM[irot].m10+" "+rotM[irot].m11+" "+rotM[irot].m12+"\n"+
					//	rotM[irot].m20+" "+rotM[irot].m21+" "+rotM[irot].m22);
				}

			} else {
				// Divided volume

				// The transform for axis = 2 is a rotation in phi, starting at
				// "start" and stepping by "step"

				double phi = deg2rad*(step*in+start);
				t3d = new Transform3D();
				t3d.rotZ(phi);

			}



			BranchGroup newBG2 = new BranchGroup();
			newBG2.setCapability(BranchGroup.ALLOW_DETACH);
			newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
			newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
			newBG2.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
				
			// Create the TransformGroup that this detector and other sub detectors will hang from

			TransformGroup newTG = new TransformGroup(t3d);
			newTG.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
			newTG.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
			newTG.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);

			newBG2.addChild(newTG);

			// We need to create a new BranchGroup for the shapes hanging off this TG
			BranchGroup newBG = new BranchGroup();
			newBG.setCapability(BranchGroup.ALLOW_DETACH);
			newBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
			newBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
			newBG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);

			newBG.setUserData("Attached");

			newTG.addChild(newBG);

			Shape3D thisShape = volShapes[ivol];

			try {
				if(thisShape != null) {
					newBG.addChild(thisShape);
				} else {
					TextMessage("Shape is NULL!");
				}
			} catch (Exception e) {
				// indicates the shape already has a parent: we need to
				// clone it and add the clone
				// TextMessage("Cloning "+thisName+" "+e);
		        Shape3D copyShape = new Shape3D(thisShape.getGeometry(), thisShape.getAppearance());
				copyShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
				copyShape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
				copyShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
				//copyShape.setUserData(thisShape.getUserData());
				newBG.addChild(copyShape);
				thisShape = copyShape;
			}


			if(!divide) {
				if(prune) {
					TextMessage(parentage+"-"+thisName+" "+nDetector+" ("+geantReader.getShapeName(ivol)+" @ "+(float)xoff+" "+(float)yoff+" "+(float)zoff+" vol "+volumesFortran[in]+" irot "+rotations[in]+") -> VISIBLE "+geantReader.getNumSubNodes(ivol));
					//TextMessage(parentage+"-"+thisName+" "+nDetector+" ("+geantReader.getShapeName(ivol)+" @ "+xoff+" "+yoff+" "+zoff+" irot "+rotations[in]+") -> VISIBLE");
					int idetApp = appInitials.indexOf(thisName.substring(0,1));
					if(idetApp < 0 || idetApp >= (nDets-1)) idetApp = nDets-1;
					Appearance app = solidAppearance[idetApp]; 
					thisShape.setAppearance(app);
				} else {
					thisShape.setAppearance(invisibleAppearance);
					TextMessage(parentage+"-"+thisName+" "+nDetector+" ("+geantReader.getShapeName(ivol)+" @ "+(float)xoff+" "+(float)yoff+" "+(float)zoff+" irot "+rotations[in]+") "+geantReader.getNumSubNodes(ivol));
					//TextMessage(parentage+"-"+thisName+" "+nDetector+" ("+geantReader.getShapeName(ivol)+" @ "+xoff+" "+yoff+" "+zoff+" irot "+rotations[in]+")");
				}
			} else {
				TextMessage(parentage+"-"+thisName+" "+nDetector+" ("+geantReader.getShapeName(ivol)+" @ "+(float)xoff+" "+(float)yoff+" "+(float)zoff+" irot "+rotations[in]+") "+geantReader.getNumSubNodes(ivol));
				thisShape.setAppearance(invisibleAppearance);
			}

			currentBG.addChild(newBG2);

			DefaultMutableTreeNode thisTN = new DefaultMutableTreeNode(thisName);
			detectorTNs.addElement(thisTN);
			currentTN.add(thisTN);

			detectorBGs.addElement(newBG);
			detectorNames.addElement(thisName);
			parentNodes.addElement(newTG);

			// We decide whether to go to the next level based on if this detector group is
			// in the list of visible detectors

			if(!prune) {
				traverseNodes(ivol, newBG, thisTN, level, "-"+prefix, parentage+"-"+thisName, maxlevel);
			}
			
		}

		//At this point we could compile the currentBG

		level--;
	}

*/

  public BranchGroup createSceneGraph() {

	TextMessage("Creating SceneGraph");

    // Create the root of the branch graph
    objRoot = new BranchGroup();
    objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
    objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_READ );
    objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
	objRoot.setCapability(BranchGroup.ALLOW_DETACH);

	TransformGroup objScale = new TransformGroup();
    t3d = new Transform3D();
	t3d.rotY(0.5f*Math.PI);
    t3d.setScale(0.0005); // This makes the initial view big enough

	objScale.setTransform(t3d);

	objRoot.addChild(objScale);

    // set up the view transform group

    CMSTrans = new TransformGroup();
    CMSTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    CMSTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    CMSTrans.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
    CMSTrans.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
    CMSTrans.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
    CMSTrans.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);

    objScale.addChild(CMSTrans);

	// Create a bounding sphere that will contain CMS
    bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 5000.0);

	createDetectorBG();

	CMSTrans.addChild(detectorBG);
		
	f3d = new Font3D(new Font(fontName, Font.PLAIN, 52),
		             new FontExtrusion());

	// Set the background colour
	Background bg = new Background(new Color3f(0.2f,0.2f,0.2f));
	bg.setApplicationBounds(bounds);
	CMSTrans.addChild(bg);

	twistBG = new BranchGroup();
    twistBG.setCapability(BranchGroup.ALLOW_DETACH);

	Alpha twistAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE |
				     Alpha.DECREASING_ENABLE,
				     0, 0,
				     2000, 1000, 200,
				     2000, 1000, 200);

	// Create the twisting behavior
	TwistBehavior tBeh = new TwistBehavior(twistAlpha,CMSTrans);
	tBeh.setSchedulingBounds(bounds);
	twistBG.addChild(tBeh);

// The lighting ...	
	lightsBG = new BranchGroup();
    lightsBG.setCapability(BranchGroup.ALLOW_DETACH);

	Color3f lColor1 = new Color3f(1.0f, 1.0f, 1.0f);
/*
	DirectionalLight dirLight1 = new DirectionalLight(
		new Color3f( 0.9f, 0.9f, 0.9f ),
		new Vector3f( -1.f, -1.0f, -1.0f ) );

	dirLight1.setInfluencingBounds(bounds);
	dirLight1.setEnable(true);
	lightsBG.addChild(dirLight1);

	DirectionalLight dirLight2 = new DirectionalLight(
		new Color3f( 0.9f, 0.9f, 0.9f ),
		new Vector3f( -1.0f, -1.0f, 0.0f ) );

	dirLight2.setInfluencingBounds(bounds);
	dirLight2.setEnable(true);
	lightsBG.addChild(dirLight2);

	AmbientLight lgt1 = new AmbientLight(lColor1);
	lgt1.setInfluencingBounds(bounds);
	lgt1.setEnable(true);

	lightsBG.addChild(lgt1);
*/


	for(int i=0;i<0;i++) {
		//SpotLight lgt7 = new SpotLight();
		DirectionalLight lgt7 = new DirectionalLight();
		//lgt7.setColor(lColor1);
		lgt7.setDirection(1f*(float)(-1+2*Math.random()),
						  1f*(float)(-1+2*Math.random()),
						  1f*(float)(-1+2*Math.random()));
		//lgt7.setSpreadAngle(0.5f*(float)Math.PI);
		//lgt7.setConcentration(0.0f);
		lgt7.setInfluencingBounds(bounds);
		lgt7.setEnable(true);

		lightsBG.addChild(lgt7);
	}

	DirectionalLight lgt7 = new DirectionalLight();
	lgt7.setColor(new Color3f( 1.0f, 1.0f, 1.0f ));
	lgt7.setDirection(-1.0f,-1.0f,-1.0f);
	//lgt7.setSpreadAngle(0.5f*(float)Math.PI);
	//lgt7.setConcentration(0.0f);
	lgt7.setInfluencingBounds(bounds);
	lgt7.setEnable(true);

	lightsBG.addChild(lgt7);

	AmbientLight lgt1 = new AmbientLight(new Color3f( 0.2f, 0.2f, 0.2f ));
	lgt1.setInfluencingBounds(bounds);
	lgt1.setEnable(true);

	lightsBG.addChild(lgt1);


	DirectionalLight lgt2 = new DirectionalLight();
	lgt2.setColor(new Color3f( 1.0f, 1.0f, 1.0f ));
	lgt2.setDirection(1.0f,1.0f,1.0f);
	//lgt7.setSpreadAngle(0.5f*(float)Math.PI);
	//lgt7.setConcentration(0.0f);
	lgt2.setInfluencingBounds(bounds);
	lgt2.setEnable(true);

	lightsBG.addChild(lgt2);
	
	/*
	DirectionalLight lgt3 = new DirectionalLight();
	lgt3.setColor(new Color3f( 1.0f, 0.0f, 0.0f ));
	lgt3.setDirection(-1.0f,0.0f,-1.0f);
	//lgt7.setSpreadAngle(0.5f*(float)Math.PI);
	//lgt7.setConcentration(0.0f);
	lgt3.setInfluencingBounds(bounds);
	lgt3.setEnable(true);

	lightsBG.addChild(lgt3);
	*/
	
	
	lighting = true;
	CMSTrans.addChild(lightsBG);

	
// Create the picking behaviour for the event objects
//	PickHighlightBehavior pickBeh = new PickHighlightBehavior(canvasCMS, objRoot, bounds);
//
	PickRotateBehavior rotateBeh = new PickRotateBehavior(objRoot, canvasCMS, bounds);
	objRoot.addChild(rotateBeh);
//
//    PickZoomBehavior zoomBeh = new PickZoomBehavior(objRoot, canvasCMS, bounds);
//    objRoot.addChild(zoomBeh);
//
//    PickTranslateBehavior translateBeh = new PickTranslateBehavior(objRoot, canvasCMS, bounds);
//    objRoot.addChild(translateBeh);


// Create the rotate behavior node
    MouseRotateY behavior = new MouseRotateY();
    behavior.setTransformGroup(CMSTrans);
    CMSTrans.addChild(behavior);
    behavior.setSchedulingBounds(bounds);

    return objRoot;
  }


	public Tag selectEventWS(int run, int event) {

		boolean found = false;
		Tag tagEvent = null;
/*

		try {

		    //shc = new SoapHttpClient(sc,new URL(proxy));	
		    shc = new SoapHttpClient(sc);	

			String methodName = "GetEvent";

			call = new RPCCall(sc,nameSpace,methodName);
			call.setParameter(	  "runNumber", 
								 ""+run,
								  Integer.class);
			call.setParameter(	  "eventNumber", 
								 ""+event,
								  Integer.class);

			Envelope answer = shc.invokeHTTP(new URL(endPoint), nameSpace+methodName, call.getEnvelope());

			if(answer.hasFault()) {
				Fault f = answer.getFault();
				TextMessage("Faultcode:   " + f.getFaultcode());
				TextMessage("Faultstring: " + f.getFaultstring());
				TextMessage("Faultactor:  " + f.getFaultactor());		
				return null;
			}
	
			// get the results
			ParamBag p = call.getResults(answer);
		
			byte [] B = (byte []) p.getValue(0);

			int runNumber = byteArrayToInt(B,0);
			int eventNumber = byteArrayToInt(B,4);

			if(runNumber == run && eventNumber == event) {
				found = true;
			} else return null;


			String OID = new String(B,8,20);
			float [] jetE = new float[5];
			float [] jetPhi = new float[5];
			float [] jetTheta = new float[5];
			float [] partE = new float[5];
			float [] partPhi = new float[5];
			float [] partTheta = new float[5];
			int [] partID = new int[5];

			int offset = 28;
			for(int i=0;i<5;i++) {
				jetE[i] = byteToFloat(B,offset);
				offset += 4;
				jetPhi[i] = byteToFloat(B,offset);
				offset += 4;
				jetTheta[i] = byteToFloat(B,offset);
				offset += 4;
				partID[i] = byteArrayToInt(B,offset);
				offset += 4;
				partE[i] = byteToFloat(B,offset);
				offset += 4;
				partPhi[i] = byteToFloat(B,offset);
				offset += 4;
				partTheta[i] = byteToFloat(B,offset);
				offset += 4;
				//TextMessage("JetE "+i+" "+jetE[i]);
			}

			//TextMessage("Decoded event");

			tagEvent = new Tag(run,event,OID,jetE,jetPhi,jetTheta,partID,partE,partPhi,partTheta);

		} catch (Exception e) {
			System.err.println("Error : "+e);
			e.printStackTrace();

			return null;
		}

		LEventNumber.setText("Event "+event);

		LRunNumber.setText("Run "+run);

*/
		return tagEvent;



	}

    public static float byteToFloat(byte b[], int pos) {
		int bits = 0;
		for(int shift = 0; shift <= 24; shift += 8)
			bits |= ((b[pos++] & 0xFF) << shift);
		//for(int shift = 0; shift < 56; shift += 8)
		//	bits |= ((b[pos++] & 0xFFL) << shift);

        return Float.intBitsToFloat(bits);
    }


    public static int byteArrayToInt(byte ba[], int offset) {
		 return (((int) ba[offset] & 0xFF) <<  0) + (((int) ba[offset+1] & 0xFF) <<  8) +
        (((int) ba[offset+2] & 0xFF) << 16) + (((int) ba[offset+3] & 0xFF) << 24);
    }


	public void attachEvent(Tag tagEvent) {

		if(eventBG != null) eventBG.detach();

		eventBG = new BranchGroup();
	    eventBG.setCapability(BranchGroup.ALLOW_DETACH);
	    eventBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
	    eventBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);

		// First attach the jets

		float Extent = 1000.f; // length of Jet

		float Hetasplit = 1.5f;
		float dHphi = 0.045f; // should be 0.09, but that looks odd ...
		float dHeta = 0.045f;

		// Set up the appearance for the Jet towers

		Tower tower;
		int nJets = 0;

		for(int ijet=0;ijet<5;ijet++) {

			double energy = tagEvent.getJetE(ijet);
			if(energy <= 0.0) break;
			nJets++;

			double theta = tagEvent.getJetTheta(ijet);
			double phi = tagEvent.getJetPhi(ijet);
			float tanth = (float) Math.tan(theta);
			float sinphi = (float) Math.sin(phi);
			float cosphi = (float) Math.cos(phi);
			float X = Extent*cosphi;
			float Y = Extent*sinphi;
			float Z = Extent/tanth;

			float cRed = 1.0f;
			if(energy < 100.) cRed = (float) (energy/100.);

			Appearance jtowerAppearance =  new Appearance();
			Material jtowerMaterial = new Material();
			PolygonAttributes pa = new PolygonAttributes();
			pa.setPolygonMode(pa.POLYGON_LINE);
			pa.setCullFace(pa.CULL_NONE); 
			jtowerAppearance.setPolygonAttributes(pa);
			TransparencyAttributes jtowerTrans =new TransparencyAttributes(TransparencyAttributes.FASTEST, 0.3f);
			jtowerAppearance.setTransparencyAttributes(jtowerTrans);
			ColoringAttributes jtowerColoring = new ColoringAttributes();
			jtowerColoring.setColor(new Color3f(cRed,1.0f,1.0f));
			jtowerMaterial.setEmissiveColor(new Color3f(cRed,1.0f,1.0f));
			jtowerAppearance.setColoringAttributes(jtowerColoring);
			jtowerAppearance.setMaterial(jtowerMaterial);

			tower = new Tower( (float)theta, (float)phi, Extent,
								2.0f, 2.0f, Hetasplit, 2.f*dHphi, 2.f*dHeta, jtowerAppearance);
			Shape3D twr = (Shape3D) tower.getChild();
			if(twr != null) eventBG.addChild(twr);
		}

		LJets.setText(nJets+" Jets");

		// Now the particles

		int nParticles = 0;

		Extent = 500.0f;

		for(int ip=0;ip<5;ip++) {

			double energy = tagEvent.getPartE(ip);
			if(energy <= 0.0) break;
			nParticles++;

			double theta = tagEvent.getPartTheta(ip);
			double phi = tagEvent.getPartPhi(ip);
			float tanth = (float) Math.tan(theta);
			float sinphi = (float) Math.sin(phi);
			float cosphi = (float) Math.cos(phi);
			float X = Extent*cosphi;
			float Y = Extent*sinphi;
			float Z = Extent/tanth;

			float cGreen = 1.0f;
			if(energy < 100.) cGreen = (float) (energy/100.);

			// Set up the appearance for the Particle towers
			Appearance partAppearance =  new Appearance();
			Material partMaterial = new Material();
			PolygonAttributes pa = new PolygonAttributes();
			pa.setPolygonMode(pa.POLYGON_FILL);
			partAppearance.setPolygonAttributes(pa);
			TransparencyAttributes partTrans =new TransparencyAttributes(TransparencyAttributes.FASTEST, 0.3f);
			partAppearance.setTransparencyAttributes(partTrans);
			ColoringAttributes partColoring = new ColoringAttributes();
			partColoring.setColor(new Color3f(0.0f,cGreen,1.0f));
			partMaterial.setEmissiveColor(new Color3f(0.0f,cGreen,1.0f));
			partAppearance.setColoringAttributes(partColoring);
			partAppearance.setMaterial(partMaterial);

			tower = new Tower( (float)theta, (float)phi, Extent,
								2.0f, 2.0f, Hetasplit, 2.f*dHphi, 2.f*dHeta, partAppearance);
			Shape3D twr = (Shape3D) tower.getChild();
			if(twr != null) eventBG.addChild(twr);
		}

		LParticles.setText(nParticles+" Particles");

		
		CMSTrans.addChild(eventBG);

	}
	
	public void destroy() {
		stop();
	}

	public String getAppletInfo () {
      return "COJAC by JJB";
	}


}

class polyCone extends Object {
	private Shape3D scone;
	private float [] qverts;
	private int vertCount = 0;
	private int normCount = 0;
	private float[] normals;
	private float mag,xn,yn,zn;

	public polyCone(int nfaces, float fphi, float fdphi, int nsections, 
		float [] rmin, float [] rmax, float [] z, Appearance coneAppearance) {


		float dphi = fdphi/nfaces;
		float phi0,phi1;

		if(nsections < 2) System.err.println("Bad call to polyCone");

		boolean full = ((fdphi-fphi) >= 2.0*Math.PI);

		qverts = new float[48*nfaces*(nsections-1)];

		normals = new float[48*nfaces*(nsections-1)];

		vertCount = 0;

		normCount = 0;

		for (int i=0;i<nfaces;i++) {

			phi0 = fphi + i*dphi; 
			phi1 = phi0 + dphi;
			float cosp0 = (float)Math.cos(phi0);
			float sinp0 = (float)Math.sin(phi0);
			float cosp1 = (float)Math.cos(phi1);
			float sinp1 = (float)Math.sin(phi1);

			for (int j=0;j<nsections-1;j++) {

				float x0 = rmin[j]*cosp0;
				float y0 = rmin[j]*sinp0;

				float x1 = rmin[j]*cosp1;
				float y1 = rmin[j]*sinp1;

				float x2 = rmax[j]*cosp0;
				float y2 = rmax[j]*sinp0;

				float x3 = rmax[j]*cosp1;
				float y3 = rmax[j]*sinp1;

				float x4 = rmin[j+1]*cosp0;
				float y4 = rmin[j+1]*sinp0;

				float x5 = rmin[j+1]*cosp1;
				float y5 = rmin[j+1]*sinp1;

				float x6 = rmax[j+1]*cosp0;
				float y6 = rmax[j+1]*sinp0;

				float x7 = rmax[j+1]*cosp1;
				float y7 = rmax[j+1]*sinp1;

				float z0 = z[j];
				float z1 = z[j+1];

				// Each group of 4 space points defines a quadrilateral to be drawn

				// 0132
                qverts[vertCount]      = x0;
                qverts[vertCount+1]    = y0;
                qverts[vertCount+2]    = z0;

				normals[normCount]     = 0f;
				normals[normCount+1]   = 0f;
				normals[normCount+2]   = -1f;

                qverts[vertCount+3]    = x1;
                qverts[vertCount+4]    = y1;
                qverts[vertCount+5]    = z0;

				normals[normCount+3]     = 0f;
				normals[normCount+4]   = 0f;
				normals[normCount+5]   = -1f;

                qverts[vertCount+6]    = x3;
                qverts[vertCount+7]    = y3;
                qverts[vertCount+8]    = z0;

				normals[normCount+6]     = 0f;
				normals[normCount+7]   = 0f;
				normals[normCount+8]   = -1f;

                qverts[vertCount+9]    = x2;
                qverts[vertCount+10]   = y2;
                qverts[vertCount+11]   = z0;

				normals[normCount+9]     = 0f;
				normals[normCount+10]   = 0f;
				normals[normCount+11]   = -1f;
				
				// 4576
                qverts[vertCount+12]   = x4;
                qverts[vertCount+13]   = y4;
                qverts[vertCount+14]   = z1;

				normals[normCount+12]     = 0f;
				normals[normCount+13]   = 0f;
				normals[normCount+14]   = 1f;

                qverts[vertCount+15]   = x5;
                qverts[vertCount+16]   = y5;
                qverts[vertCount+17]   = z1;

				normals[normCount+15]     = 0f;
				normals[normCount+16]   = 0f;
				normals[normCount+17]   = 1f;

                qverts[vertCount+18]   = x7;
                qverts[vertCount+19]   = y7;
                qverts[vertCount+20]   = z1;

				normals[normCount+18]     = 0f;
				normals[normCount+19]   = 0f;
				normals[normCount+20]   = 1f;

                qverts[vertCount+21]   = x6;
                qverts[vertCount+22]   = y6;
                qverts[vertCount+23]   = z1;

				normals[normCount+21]     = 0f;
				normals[normCount+22]   = 0f;
				normals[normCount+23]   = 1f;

				// 0154 
                qverts[vertCount+24]   = x0;
                qverts[vertCount+25]   = y0;
                qverts[vertCount+26]   = z0;

				normals[normCount+24]     = -cosp0;
				normals[normCount+25]   = -sinp0;
				normals[normCount+26]   = 0f;

                qverts[vertCount+27]   = x1;
                qverts[vertCount+28]   = y1;
                qverts[vertCount+29]   = z0;

				normals[normCount+27]     = -cosp1;
				normals[normCount+28]   = -sinp1;
				normals[normCount+29]   = 0f;

                qverts[vertCount+30]   = x5;
                qverts[vertCount+31]   = y5;
                qverts[vertCount+32]   = z1;

				normals[normCount+30]     = -cosp1;
				normals[normCount+31]   = -sinp1;
				normals[normCount+32]   = 0f;

                qverts[vertCount+33]   = x4;
                qverts[vertCount+34]   = y4;
                qverts[vertCount+35]   = z1;

				normals[normCount+33]     = -cosp0;
				normals[normCount+34]   = -sinp0;
				normals[normCount+35]   = 0f;

				// 2376
                qverts[vertCount+36]   = x2;
                qverts[vertCount+37]   = y2;
                qverts[vertCount+38]   = z0;

				normals[normCount+36]     = cosp0;
				normals[normCount+37]   = sinp0;
				normals[normCount+38]   = 0f;

                qverts[vertCount+39]   = x3;
                qverts[vertCount+40]   = y3;
                qverts[vertCount+41]   = z0;

				normals[normCount+39]     = cosp1;
				normals[normCount+40]   = sinp1;
				normals[normCount+41]   = 0f;

                qverts[vertCount+42]   = x7;
                qverts[vertCount+43]   = y7;
                qverts[vertCount+44]   = z1;

				normals[normCount+42]     = cosp1;
				normals[normCount+43]   = sinp1;
				normals[normCount+44]   = 0f;

                qverts[vertCount+45]   = x6;
                qverts[vertCount+46]   = y6;
                qverts[vertCount+47]   = z1;

				normals[normCount+45]     = cosp0;
				normals[normCount+46]   = sinp0;
				normals[normCount+47]   = 0f;

				vertCount += 48;
				normCount += 48;
            }
		}

		QuadArray coneGeometry = new QuadArray( vertCount/3, QuadArray.COORDINATES | QuadArray.NORMALS);
 
        coneGeometry.setCapability(QuadArray.ALLOW_COORDINATE_WRITE );
		coneGeometry.setCapability(QuadArray.ALLOW_COLOR_WRITE);
		coneGeometry.setCapability(QuadArray.ALLOW_COORDINATE_READ);
		coneGeometry.setCapability(QuadArray.ALLOW_COUNT_READ);
		coneGeometry.setCapability(QuadArray.ALLOW_FORMAT_READ);

        coneGeometry.setCoordinates( 0, qverts );

		coneGeometry.setNormals(0,normals);

        scone = new Shape3D(coneGeometry, coneAppearance);
	}

	public Shape3D getChild() {
        return scone;
    }

}



class trapezoid extends Object {
        private Shape3D strapezoid;
        private float[] qverts;
		private float[] normals;
        private int vertCount = 0;
        private int normCount = 0;

		private float mag,xn,yn,zn;
 
        public trapezoid(float dx1, float dx2, float dy, float dz, Appearance trapezoidAppearance) {

			vertCount = 72;
			normCount = 72;
			qverts = new float[vertCount];
			normals = new float[normCount];

			normCount = 0;

			float x0 = -dx1;
			float x1 = +dx1;
			float x2 = -dx2;
			float x3 = +dx2;
			float y0 = -dy;
			float y1 = +dy;
			float z0 = -dz;
			float z1 = +dz;

			// Each group of 4 space points defines a quadrilateral to be drawn

			// front face
            qverts[0]    = x0;
            qverts[1]    = y0;
            qverts[2]    = z0;

            qverts[3]    = x1;
            qverts[4]    = y0;
            qverts[5]    = z0;

			qverts[6]    = x1;
            qverts[7]    = y1;
            qverts[8]    = z0;

            qverts[9]    = x0;
            qverts[10]   = y1;
            qverts[11]   = z0;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 0f;
				normals[normCount++] = 0f;
				normals[normCount++] = -1f;
			}
			
			// right face
            qverts[12]   = x1;
            qverts[13]   = y0;
            qverts[14]   = z0;

            qverts[15]   = x3;
            qverts[16]   = y0;
            qverts[17]   = z1;

			qverts[18]   = x3;
            qverts[19]   = y1;
            qverts[20]   = z1;

            qverts[21]   = x1;
            qverts[22]   = y1;
            qverts[23]   = z0;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 1f;
				normals[normCount++] = 0f;
				normals[normCount++] = 0f;
			}

			// bottom face
            qverts[24]   = x0;
            qverts[25]   = y0;
            qverts[26]   = z0;

            qverts[27]   = x1;
            qverts[28]   = y0;
            qverts[29]   = z0;

			qverts[30]   = x3;
            qverts[31]   = y0;
            qverts[32]   = z1;

            qverts[33]   = x2;
            qverts[34]   = y0;
            qverts[35]   = z1;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 0f;
				normals[normCount++] = -1f;
				normals[normCount++] = 0f;
			}

			// left face
            qverts[36]   = x0;
            qverts[37]   = y0;
            qverts[38]   = z0;

            qverts[39]   = x0;
            qverts[40]   = y1;
            qverts[41]   = z0;

			qverts[42]   = x2;
            qverts[43]   = y1;
            qverts[44]   = z1;

            qverts[45]   = x2;
            qverts[46]   = y0;
            qverts[47]   = z1;

			for(int i=0;i<4;i++) {
				normals[normCount++] = -1f;
				normals[normCount++] = 0f;
				normals[normCount++] = 0f;
			}

			// top face
            qverts[48]   = x0;
            qverts[49]   = y1;
            qverts[50]   = z0;

            qverts[51]   = x1;
            qverts[52]   = y1;
            qverts[53]   = z0;

			qverts[54]   = x3;
            qverts[55]   = y1;
            qverts[56]   = z1;

            qverts[57]   = x2;
            qverts[58]   = y1;
            qverts[59]   = z1;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 0f;
				normals[normCount++] = 1f;
				normals[normCount++] = 0f;
			}

			// back face
            qverts[60]   = x2;
            qverts[61]   = y1;
            qverts[62]   = z1;

            qverts[63]   = x3;
            qverts[64]   = y1;
            qverts[65]   = z1;

			qverts[66]   = x3;
            qverts[67]   = y0;
            qverts[68]   = z1;

            qverts[69]   = x2;
            qverts[70]   = y0;
            qverts[71]   = z1;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 0f;
				normals[normCount++] = 0f;
				normals[normCount++] = 1f;
			}

            QuadArray trapezoidGeometry = new QuadArray( vertCount/3, 
                  QuadArray.COORDINATES | QuadArray.NORMALS);
 
            trapezoidGeometry.setCapability(QuadArray.ALLOW_COORDINATE_WRITE );
			trapezoidGeometry.setCapability(QuadArray.ALLOW_COLOR_WRITE);
			trapezoidGeometry.setCapability(QuadArray.ALLOW_COORDINATE_READ);
			trapezoidGeometry.setCapability(QuadArray.ALLOW_COUNT_READ);
			trapezoidGeometry.setCapability(QuadArray.ALLOW_FORMAT_READ);

            trapezoidGeometry.setCoordinates( 0, qverts );
			trapezoidGeometry.setNormals(0,normals);

            strapezoid = new Shape3D(trapezoidGeometry, trapezoidAppearance);
        }

        public Shape3D getChild() {
            return strapezoid;
        }
}

class gentrapezoid extends Object {
        private Shape3D strapezoid;
        private float[] qverts;
		private float[] normals;
        private int vertCount = 0;
        private int normCount = 0;

		private float mag,xn,yn,zn;
 
		public gentrapezoid(float fDz, float ftheta, float fphi, float fH1, float fBl1, float fTl1,
			float falpha1, float fH2, float fBl2, float fTl2, float falpha2, Appearance trapezoidAppearance) {
/*

TRAP is a general trapezoid. The faces perpendicular to z are trapezia and
 their centres are not necessarily on a line parallel to the z axis.

     - dz         half-length along the z axis
     - theta      polar angle of the line joining the centre of the face
                  at -DZ to the centre of the one at +DZ
     - phi        azimuthal angle of the line joining the centre of the face
                  at -DZ to the centre of the one at +DZ
     - h1         half-length along y of the face at -DZ
     - bl1        half-length along x of the side at -H1 in y of the face
                  at -DZ in z
     - tl1        half-length along x of the side at +H1 in y of the face
                  at -DZ in z
     - alpha1     angle with respect to the y axis from the centre of the
                  side at -H1 in y to the centre of the side at +H1 in y
                  of the face at -DZ in z
     - h2         half-length along y of the face at +DZ
     - bl2        half-length along x of the side at -H2 in y of the
                  face at +DZ in z
     - tl2        half-length along x of the side at +H2 in y of the face
                  at +DZ in z
     - alpha2     angle with respect to the y axis from the centre of the side
                  at -H2 in y to the centre of the side at +H2 in y of the
                  face at +DZ in z


*/
			vertCount = 72;
			normCount = 72;
			qverts = new float[vertCount];
			normals = new float[normCount];

			normCount = 0;

			// Mathematics hiked from ROOT

			float tth = (float)Math.tan(ftheta);
			float tx = tth*(float)Math.cos(fphi);
			float ty = tth*(float)Math.sin(fphi);
			float tth1 = (float)Math.tan(falpha1);
			float tth2 = (float)Math.tan(falpha2);

			float z0 = -fDz;
			float z1 = +fDz;

			float y0 = -fH1-fDz*ty;
			float y1 = +fH1-fDz*ty;
			float y2 = +fH2+fDz*ty;
			float y3 = -fH2+fDz*ty;

			float x0 = -fDz*tx-tth1*fH1-fBl1;  
			float x1 = -fDz*tx-tth1*fH1+fBl1; 
			float x2 = -fDz*tx+tth1*fH1+fTl1; 
			float x3 = -fDz*tx+tth1*fH1-fTl1;  

			float x4 =  fDz*tx-tth2*fH2-fBl2;  
			float x5 =  fDz*tx-tth2*fH2+fBl2; 
			float x6 =  fDz*tx+tth2*fH2+fTl2;  
			float x7 =  fDz*tx+tth2*fH2-fTl2;  
    
			// Each group of 4 space points defines a quadrilateral to be drawn

			// front face
            qverts[0]    = x0;
            qverts[1]    = y0;
            qverts[2]    = z0;

            qverts[3]    = x1;
            qverts[4]    = y0;
            qverts[5]    = z0;

			qverts[6]    = x2;
            qverts[7]    = y1;
            qverts[8]    = z0;

            qverts[9]    = x3;
            qverts[10]   = y1;
            qverts[11]   = z0;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 0f;
				normals[normCount++] = 0f;
				normals[normCount++] = -1f;
			}
			
			// right face
            qverts[12]   = x1;
            qverts[13]   = y0;
            qverts[14]   = z0;

            qverts[15]   = x5;
            qverts[16]   = y3;
            qverts[17]   = z1;

			qverts[18]   = x6;
            qverts[19]   = y2;
            qverts[20]   = z1;

            qverts[21]   = x2;
            qverts[22]   = y1;
            qverts[23]   = z0;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 1f;
				normals[normCount++] = 0f;
				normals[normCount++] = 0f;
			}

			// bottom face
            qverts[24]   = x0;
            qverts[25]   = y0;
            qverts[26]   = z0;

            qverts[27]   = x1;
            qverts[28]   = y0;
            qverts[29]   = z0;

			qverts[30]   = x5;
            qverts[31]   = y3;
            qverts[32]   = z1;

            qverts[33]   = x4;
            qverts[34]   = y3;
            qverts[35]   = z1;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 0f;
				normals[normCount++] = -1f;
				normals[normCount++] = 0f;
			}

			// left face
            qverts[36]   = x0;
            qverts[37]   = y0;
            qverts[38]   = z0;

            qverts[39]   = x3;
            qverts[40]   = y1;
            qverts[41]   = z0;

			qverts[42]   = x7;
            qverts[43]   = y2;
            qverts[44]   = z1;

            qverts[45]   = x4;
            qverts[46]   = y3;
            qverts[47]   = z1;

			for(int i=0;i<4;i++) {
				normals[normCount++] = -1f;
				normals[normCount++] = 0f;
				normals[normCount++] = 0f;
			}

			// top face
            qverts[48]   = x2;
            qverts[49]   = y1;
            qverts[50]   = z0;

            qverts[51]   = x6;
            qverts[52]   = y2;
            qverts[53]   = z1;

			qverts[54]   = x7;
            qverts[55]   = y2;
            qverts[56]   = z1;

            qverts[57]   = x3;
            qverts[58]   = y1;
            qverts[59]   = z0;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 0f;
				normals[normCount++] = 1f;
				normals[normCount++] = 0f;
			}

			// back face
            qverts[60]   = x6;
            qverts[61]   = y2;
            qverts[62]   = z1;

            qverts[63]   = x7;
            qverts[64]   = y2;
            qverts[65]   = z1;

			qverts[66]   = x4;
            qverts[67]   = y3;
            qverts[68]   = z1;

            qverts[69]   = x5;
            qverts[70]   = y3;
            qverts[71]   = z1;

			for(int i=0;i<4;i++) {
				normals[normCount++] = 0f;
				normals[normCount++] = 0f;
				normals[normCount++] = 1f;
			}

            QuadArray trapezoidGeometry = new QuadArray( vertCount/3, 
                  QuadArray.COORDINATES | QuadArray.NORMALS);
 
            trapezoidGeometry.setCapability(QuadArray.ALLOW_COORDINATE_WRITE );
			trapezoidGeometry.setCapability(QuadArray.ALLOW_COLOR_WRITE);
			trapezoidGeometry.setCapability(QuadArray.ALLOW_COORDINATE_READ);
			trapezoidGeometry.setCapability(QuadArray.ALLOW_COUNT_READ);
			trapezoidGeometry.setCapability(QuadArray.ALLOW_FORMAT_READ);

            trapezoidGeometry.setCoordinates( 0, qverts );
			trapezoidGeometry.setNormals(0,normals);

            strapezoid = new Shape3D(trapezoidGeometry, trapezoidAppearance);
        }

        public Shape3D getChild() {
            return strapezoid;
        }
}




class Tower extends Object
{
        private Shape3D stower;
        private float[] qverts;
        private int vertCount = 0;
        private int normalcount = 0;
        private Vector3f[] normals;

		private float R;
		private float Z;
		private float etasplit;
		private float dphi = 0.0175f;
		private float deta = 0.0175f;

		private float mag,xn,yn,zn;
 
        public  Tower(float theta, float phi, float height, 
					float _r, float _z, float _etasplit, float _dphi, float _deta, 
					Appearance towerAppearance) {

			R = _r;
			Z = _z;
			etasplit = _etasplit;
			dphi = _dphi;
			deta = _deta;


			float eta = -(float)Math.log(Math.tan(0.5f*theta));

			float [] x = new float[8];
			float [] y = new float[8];
			float [] z = new float[8];

			float dtheta = deta*(float)Math.sin(theta);

			int icount = 0;
			for (int i=-1;i<2;i+=2) {
				float thistheta = theta + i*dtheta;
				for (int j=-1;j<2;j+=2) {
					float thisphi = phi + j*dphi;
					float tanth = (float) Math.tan(thistheta);
					float sinth = (float) Math.sin(thistheta);
					float costh = (float) Math.cos(thistheta);
					float sinphi = (float) Math.sin(thisphi);
					float cosphi = (float) Math.cos(thisphi);
					if(Math.abs(eta) > etasplit) {
						// Endcap
						x[icount] = Z*tanth*cosphi;
						y[icount] = Z*tanth*sinphi;
						z[icount] = Z;
					} else {
						// Barrel
						x[icount] = R*cosphi;
						y[icount] = R*sinphi;
						z[icount] = R/tanth;
					}

					x[icount+1] = x[icount] + height*sinth*cosphi;
					y[icount+1] = y[icount] + height*sinth*sinphi;
					z[icount+1] = z[icount] + height*costh;

					icount += 2;
				}
			}

			int index [] = {0,1,3,2,2,3,7,6,6,7,5,4,4,5,1,0,1,3,7,5,0,2,6,4};

			vertCount = 24*3;
			qverts = new float[vertCount];

			for(int i=0;i<24;i++) {
				int ind = index[i];
				int i3 = i*3;
				qverts[i3]   = x[ind];
				qverts[i3+1] = z[ind];
				qverts[i3+2] = y[ind];
			}

            QuadArray towerGeometry = new QuadArray( vertCount/3, 
                  QuadArray.COORDINATES | QuadArray.NORMALS);
 
            towerGeometry.setCapability(QuadArray.ALLOW_COORDINATE_WRITE );
			towerGeometry.setCapability(QuadArray.ALLOW_COLOR_WRITE);
			towerGeometry.setCapability(QuadArray.ALLOW_COORDINATE_READ);
			towerGeometry.setCapability(QuadArray.ALLOW_COUNT_READ);
			towerGeometry.setCapability(QuadArray.ALLOW_FORMAT_READ);

            towerGeometry.setCoordinates( 0, qverts );
  
            // Calculate normals of all points.
            normals = new Vector3f[vertCount/3];
            for (int s = 0; s < vertCount; s += 3){    
                 Vector3f norm = new Vector3f(0.0f, 0.0f, 0.0f);
                 mag = qverts[s] * qverts[s] + qverts[s+1] * 
                                        qverts[s+1] + qverts[s+2] * qverts[s+2];
                 if (mag != 0.0) { 
                     mag = 1.0f / ((float) Math.sqrt(mag)); 
                     xn = qverts[s]*mag; 
                     yn = qverts[s+1]*mag; 
                     zn = qverts[s+2]*mag;
                     norm = new Vector3f(xn, yn, zn);
                 }                       
                 normals[normalcount] = norm;
                 towerGeometry.setNormal(normalcount, norm);
                 normalcount++;
            }

            stower = new Shape3D(towerGeometry, towerAppearance);
        }

        public Shape3D getChild() {
            return stower;
        }
}










           
