/* vrmlviewpoints.java applet Copyright (C) 2000 Robert Walker E-mail vrml_viewpoints_applet@rcwalker.freeserve.co.uk Makes VRML Viewpoint from coords + coords of point to look at + tilt. Tilt angle is in degrees. Method is to find: Direction cosines for vector in direction of view. Angle to turn about x to move y coord to desired pos, then, angle about y to move x, z to desired pos, if tilt is non zero, add it as third rotation. Matrices for each rotation, multiply together to get resulting rotation. Axis as solution to (r-I).v==0 where r = rotation matrix, I = identity matrix, v = axis. Finally, find angle of rotation about new axis using formula for matrix for a rotation by an arbitrary angle about that axis, and solve to find rotation that gives the matrix r. My VRML home page is go.to/v.t You are welcome to copy /modify this code and use it in your programs. */ import java.applet.Applet; import java.util.*; import java.lang.*; import java.awt.*; import java.awt.event.*; import java.text.*; class controlspanel2 extends Panel { Button makeviewpointbutton; Label fill1,fill2; controlspanel2() { fill1=new Label(); fill2=new Label(); setLayout(new GridLayout(1,3)); add(fill1); makeviewpointbutton = new Button("Make Viewpoint"); add(makeviewpointbutton); add(fill2); } } class controlspanel1 extends Panel { TextField descriptiontext,fromtext, totext, tilttext; Label descriptionlabel,tolabel,fromlabel,tiltlabel; controlspanel2 panel2=new controlspanel2(); controlspanel1() { setLayout(new GridLayout(10,1)); descriptionlabel = new Label("View description", Label.CENTER); add(descriptionlabel); descriptiontext = new TextField(1024); add(descriptiontext); descriptiontext.setText("Viewpoint"); fromlabel = new Label("View from", Label.CENTER); add(fromlabel); fromtext = new TextField(1024); add(fromtext); fromtext.setText("0 0 10"); tolabel = new Label("View to", Label.CENTER); add(tolabel); totext = new TextField(1024); totext.setText("0 0 0"); add(totext); tiltlabel = new Label("Tilt (in degrees)", Label.CENTER); add(tiltlabel); tilttext = new TextField(1024); add(tilttext); tilttext.setText("0"); add(panel2); } } public class vrmlviewpoints extends Applet implements ActionListener { TextArea viewpointtext; Label descriptionlabel,tolabel,fromlabel,tiltlabel; double view_tilt=0; double view_field_of_view=((float)0.785398); double[] view_from = new double[3];//Viewpoint coords double[] view_to = new double[3];//point to look at controlspanel1 panel1; double PI=3.14159265358979323851; double TWO_PI=2*PI; double angles_mult=TWO_PI/360;//input angles in degrees - used for view_tilt String ViewpointName = new String("Viewpoint"); double resolution=1e-4; public void init() { setLayout(new GridLayout(2,1)); panel1=new controlspanel1(); add(panel1); panel1.panel2.makeviewpointbutton.addActionListener(this); viewpointtext = new TextArea(1024,12); add(viewpointtext); } public void actionPerformed(ActionEvent e) { if(e.getSource() == panel1.panel2.makeviewpointbutton) { StringBuffer strbuffer = new StringBuffer(1024); StringTokenizer st = new StringTokenizer(panel1.fromtext.getText()); int i=0; while (st.hasMoreTokens()) { double d; String str=st.nextToken(); d=new Double(str).doubleValue();; view_from[i]=d; i=i+1; if(i>=3) break; } i=0; StringTokenizer st2 = new StringTokenizer(panel1.totext.getText()); while (st2.hasMoreTokens()) { double d; String str=st2.nextToken(); d=new Double(str).doubleValue(); view_to[i]=d; i=i+1; if(i>=3) break; } StringTokenizer st3 = new StringTokenizer(panel1.tilttext.getText()); i=0; while (st3.hasMoreTokens()) { double d; String str=st3.nextToken(); d=new Double(str).doubleValue();; view_tilt=d*angles_mult; break; } ViewpointName=panel1.descriptiontext.getText(); MakeViewpoint(strbuffer); viewpointtext.setText(strbuffer.toString()); } } private boolean is_solution(double a[][],double x[]) { int i,j; for(j=0;j<3;j++) { double sum=0; for(i=0;i<3;i++) sum+=a[j][i]*x[i]; if((Math.abs(sum)>=resolution)) return false; } return true; } private int solve_sim_eqs_for_non_zero_sol(double a[][],double x[]) //coefficients, solution //solves system of 3 simultaneous equations //arguments should be //double a[3][3]; ==coefficients //double x[3]; == solution //resolution is absolute value below which entries are to be regarded as zero //Each row of coefficients has the coefficients for one of the equations. //The equations are //a[j][0]*x[0] +a[j][1]*x[1]+a[j][2]*x[2]==0 //looks for solution with at least one non zero value of x[]. //method is, try eliminating 0th, then 1st, then 2nd coefficient. For each one, try eliminating //it using 1st, 2nd, or 3rd equation as the one to subtract //returns 1 for success { int i,j,row_to_subtract,ith,jth; double[][] A=new double[3][3];//scratch array for calcs - reset from a[][] each time //check to see if default axis is a solution - can happen e.g. if all entries in r are zero x[0]=x[2]=0;x[1]=1; if(is_solution(a,x)) return 1; for(row_to_subtract=0;row_to_subtract<3;row_to_subtract++) for(ith=0;ith<3;ith++) { if(Math.abs(a[row_to_subtract][ith])>=resolution) { //reset scratch array ready to start new elimination for(i=0;i<3;i++) for(j=0;j<3;j++) A[j][i]=a[j][i]; //eliminate ith coefficient from all other rows with ith coefficient non zero for(jth=0;jth<3;jth++) if(jth!=row_to_subtract) { //find multiplier double mki=A[jth][ith]/A[row_to_subtract][ith];//so mki*A[row_to_subtract][ith]==A[jth][ith] //subtract row_to_subtract*multiplier from jth row if(mki!=0) for(i=0;i<3;i++) { A[jth][i]-=mki*A[row_to_subtract][i]; if(Math.abs(A[jth][i])=resolution) { x[i1]=1; x[i2]=-A[j2][i1]/A[j2][i2]; } else if(Math.abs(A[j2][i1])>=resolution) { x[i2]=1; x[i1]=-A[j2][i2]/A[j2][i1]; } else //only solution to the transformed equations is x[i1]==x[i2]==x[i3]==0 continue; //use A[j3][i1]*x[i1]+A[j3][i2]*x[i2]+A[j3][i3]*x[i3]==0 to find x[i3] if(Math.abs(A[j3][i3])>=resolution) x[i3]=-(A[j3][i1]*x[i1]+A[j3][i2]*x[i2])/A[j3][i3]; else continue;//only solution to the transformed equations is x[i1]==x[i2]==x[i3]==0 //check it is indeed a solution if(is_solution(a,x)) return 1; } } } x[0]=x[2]=0;x[1]=1;//may as well return default axis - shouldn't happen return 0; } private double FastSetInRange(double theta,double min,double increment) { return (double)((min)+(increment)*(((theta)-(min))/(increment)-Math.floor(((theta)-(min))/(increment)))); } private void MakeMatrixForRotation(double Mat[][],double theta, double ax,double by,double cz) { double a=ax,b=by,c=cz; Mat[0][0]=a*a*(1-Math.cos(theta))+Math.cos(theta); Mat[0][1]=a*b*(1-Math.cos(theta))-c*Math.sin(theta); Mat[0][2]=a*c*(1-Math.cos(theta))+b*Math.sin(theta); Mat[1][0]=a*b*(1-Math.cos(theta))+c*Math.sin(theta); Mat[1][1]=b*b*(1-Math.cos(theta))+Math.cos(theta); Mat[1][2]=b*c*(1-Math.cos(theta))-a*Math.sin(theta); Mat[2][0]=a*c*(1-Math.cos(theta))-b*Math.sin(theta); Mat[2][1]=b*c*(1-Math.cos(theta))+a*Math.sin(theta); Mat[2][2]=c*c*(1-Math.cos(theta))+Math.cos(theta); } private void MatMult(double Mat1[][],double Mat2[][],double Result[][]) { int i,j; for(i=0;i<3;i++) for(j=0;j<3;j++) { int k; Result[i][j]=0; for(k=0;k<3;k++) Result[i][j]+=Mat1[i][k]*Mat2[k][j]; } } private void MakeViewpoint(StringBuffer stringbuffer) { double[][] Mat1 = new double[3][3],Mat2 = new double[3][3],Mat3 = new double[3][3]; double[][] Result = new double[3][3]; double[][] r = new double[3][3]; double[] new_axis = new double[3]; double theta=0; double[] diff= new double[3]; int i; double th_about_z,th_about_y,th_about_x; double radius; double[] diffn= new double[3]; double ldiffn=0; int ok_sol=0; //normalised diff - direction cosines for(i=0;i<3;i++) diff[i]=view_to[i]-view_from[i]; for(i=0;i<3;i++) ldiffn+=diff[i]*diff[i]; for(i=0;i<3;i++) { diffn[i]=diff[i]/(double)Math.sqrt(ldiffn); diffn[i]=Math.min(1,Math.max(-1,diffn[i]));//in case rounding error takes its abs value over 1 } //need to apply tilt first, then angle about x, then finally, about y th_about_z=view_tilt; //angle about x is PI/2 + angle from vertical (y) (yields -ve Z axis if all angles 0) //have ndiff[1]==cos(angle from vertical) th_about_x=(double)Math.asin(diffn[1]); th_about_x=FastSetInRange((double)th_about_x,-PI,TWO_PI); //find th_about_y - angle about y axis (vertical) { double x=-diffn[2],y=-diffn[0]; radius=(double)Math.max(1e-10,(float)Math.sqrt(x*x+y*y)); double ratio=Math.min(1,Math.max(-1,x/radius));//in case rounding error takes its abs value over 1 th_about_y=(double)Math.acos(ratio); if(radius==0) th_about_y=0; if(y<0)th_about_y=-th_about_y; th_about_y=FastSetInRange(th_about_y,-PI,TWO_PI); } if((Math.abs(radius)<1e-4)) th_about_y=0; if(th_about_z!=0) MakeMatrixForRotation(Mat1,th_about_z,0,0,1); MakeMatrixForRotation(Mat2,th_about_x,1,0,0); MakeMatrixForRotation(Mat3,th_about_y,0,1,0); //Find Mat3.Mat2.Mat1 - leave out Mat1 if theta_about_z==0 if(th_about_z==0) MatMult(Mat3,Mat2,r); else { MatMult(Mat2,Mat1,Result); MatMult(Mat3,Result,r); } //Now need to find axis of rotation for result //want x with (r-I)x=0 //where r==rotation array r[0][0]-=1; r[1][1]-=1; r[2][2]-=1; //want to solve //r[0][0]*x[0]+r[0][1]*x[1]+r[0][2]*x[2]==0 //Hr[1][0]*x[0]+r[1][1]*x[1]+r[1][2]*x[2]==0 //r[2][0]*x[0]+r[2][1]*x[1]+r[2][2]*x[2]==0 //where x==new_axis ok_sol=solve_sim_eqs_for_non_zero_sol(r,new_axis); if(ok_sol>0) { //convert new_axis to direction cosines double a=new_axis[0],b=new_axis[1],c=new_axis[2]; double length=(double)Math.sqrt(a*a+b*b+c*c); a/=length; b/=length; c/=length; new_axis[0]=a;new_axis[1]=b;new_axis[2]=c; for(i=0;i<3;i++) { double d=new_axis[i]; if(Math.abs(d)<1e-4) new_axis[i]=0; } //Now need new angle //Solve r.(0,0,-1)=diffn where r is rotation about new axis //so need to look at 3rd col of the rotation matrix for rotation //by theta about new axis //and want //a*c*(1-cos(theta))+b*sin(theta)==-diffn[0] //b*c*(1-cos(theta))-a*sin(theta)==-diffn[1] //c*c*(1-cos(theta))+cos(theta)==-diffn[2] //so //(1-c*c)cos(theta)==-diffn[2]-(c*c); if((Math.abs(1-c*c)<1e-4)) //rotation about z axis {new_axis[0]=new_axis[1]=0;new_axis[2]=1;theta=FastSetInRange(view_tilt,-PI,TWO_PI);} else { double arg=(-diffn[2]-c*c)/(1-c*c); arg=Math.min(1,Math.max(-1,arg));//in case rounding error takes its abs value over 1 theta=(double)Math.acos(arg); theta=FastSetInRange(theta,-PI,TWO_PI); { double cos_theta=Math.cos(theta),sin_theta=Math.sin(theta); double t1=a*c*(1-cos_theta)+b*sin_theta;//ti should be -Result2[i][2] to reconstruct original matrix using theta double t2=b*c*(1-cos_theta)-a*sin_theta; double t3=c*c*(1-cos_theta)+cos_theta; { if ( !(Math.abs(t1+diffn[0])<1e-4) ||!(Math.abs(t2+diffn[1])<1e-4) ||!(Math.abs(t3+diffn[2])<1e-4) ) theta=TWO_PI-theta; theta=FastSetInRange(theta,-PI,TWO_PI); } } } } DecimalFormat coord = new DecimalFormat("#0.######"); stringbuffer.append("Viewpoint"+"\r\n"+"{"+"\r\n"+" description \""+ViewpointName +"\""+"\r\n"+" position "+view_from[0]+" "+view_from[1]+" "+view_from[2] +"\r\n"+" orientation "+coord.format(new_axis[0])+" "+coord.format(new_axis[1])+" "+coord.format(new_axis[2])+" "+coord.format(theta) +"\r\n"+" fieldOfView "+coord.format(view_field_of_view)+"\r\n"+"}"+"\r\n"+"\r\n" ); if(ok_sol==0) stringbuffer.append("no solution found for rotation axis - using default"); } }