1、在 Java 3D 中载入外部 3DJava 3D 虽然能支持众多的外部 3D 模型文件,但能支持被 Java 3D 使用的外部模型文件仅为.obj 和.lwd 两种;分别对应ObjectFile 类和 Lw3dLoader 类。相比之下几款主流的 3D 建模软件都能生成.obj 格式的文件,因此本文主要介绍使用 ObjectFile 类载入.obj 文件的方法。ObjectFile 类有三个构造方法,分别为:ObjectFile() ObjectFile(int flags) ObjectFile(int flags, float radians) 其中 flags 为一个整型的常量参数,用
2、于决定载入的 3D 模型以什么方式生成。参数 radians 用于决定载入模型的可显示半径。flags 参数可在以下四个值之间任取一个或者用逻辑或(“|“ )将几个参数组合使用。ObjectFile.RESIZE:忽略被载入的模型大小,直接把载入的模型放在一个范围在(1,1,1) 到(-1,-1,-1)之间的立方体空间内,并把坐标原点设为(0,0,0)。ObjectFile.REVERSE:反转载入的外部模型,即可能看模型的后面。ObjectFile.TRIANGULATE:将模型的面以三角形方式显示,此参数主要便于观察模型凹凸面。ObjectFile.STRIPIFY:以模型文件内模型的实际
3、情况显示,此参数也是默认参数。当初始化了 ObjectFile 对象后就可以用 load 方法载入.obj 格式的文件,如果模型中已包含了贴图和光照的话也将一起被载入。load 方法需要一个参数用于指出.obj 格式文件所在的路径,load 方法有多个重载方法可以使参数即可以接受String 类的值也可以接受 Url 类的值或者从输入法流读入,如果模型载入成功将返回一个 Scene 类的对象,如果载入失败将抛异常。Java 3D 针对 load 方法定义了三个异常类:FileNotFoundException 类:表示文件未找到。IncorrectFormatException 类:表示文件格
4、式不正确。ParsingErrorException 类:装载器解析文件时出错。下面的代码用以演示如何用 load 方法将一个模型载入到 Scene 类的实例:view plaincopy to clipboardprint?Scene loadScene = null; int flag = ObjectFile.STRIPIFY; ObjectFile obj = new ObjectFile(flag); try loadScene = obj.load(this.getClass().getClassLoader().getResource(filename); catch (File
5、NotFoundException e) System.out.println(“文件未找到或文件路径不正确 “); e.printStackTrace(); catch (IncorrectFormatException e) System.out.println(“文件格式不正确“); e.printStackTrace(); catch (ParsingErrorException e) System.out.println(“装载器解析文件时出错“); e.printStackTrace(); Scene loadScene = null;int flag = ObjectFile.S
6、TRIPIFY;ObjectFile obj = new ObjectFile(flag);try loadScene = obj.load(this.getClass().getClassLoader().getResource(filename); catch (FileNotFoundException e) System.out.println(“文件未找到或文件路径不正确 “);e.printStackTrace(); catch (IncorrectFormatException e) System.out.println(“文件格式不正确“);e.printStackTrace(
7、); catch (ParsingErrorException e) System.out.println(“装载器解析文件时出错“);e.printStackTrace(); 虽然至此我们已经载入了一个.obj 格式文件的 3D 模型,但把它载入到场景后我们却模型并没有按我们想像的那么显示。对比在 3D 建模工具中看到模型的样子,我们的模型被绕 X 轴逆时针的旋转了 90 度,这主要是 Java 3D 的坐标系和大多数的 3D 建模工具的坐标系不同。我们假设用户的显示器是垂直于桌面上,那么在Java 3D 中显示器的宽代表 X 轴,显示器的高代表 Y 轴,显示器垂直朝向用户的方法为 Z 轴(
8、此方向也是 Z 轴的正数方向)。而多数的建模工具使用的是世界坐标系,即将显示器的高代表 Z 轴。因此我们需要在程序将模型绕 X 轴顺时针旋转 90 度,旋转轴坐标的方法是使用Transform3D 类的 rotX 方法,相应的还有 rotY 和 rotZ 方法。view plaincopy to clipboardprint?Transform3D t3d = new Transform3D(); t3d.rotX(-Math.PI/2); TransformGroup tg = new TransformGroup(t3d); tg.addChild(loadScene.getSceneGr
9、oup); Transform3D t3d = new Transform3D();t3d.rotX(-Math.PI/2);TransformGroup tg = new TransformGroup(t3d);tg.addChild(loadScene.getSceneGroup);注意:这里有一个容易混淆的概念,就是我们刚才的步骤是旋转的坐标系,而不是模型,模型是附加在坐标系的上,没有法被旋转。而在刚才的步骤完成后就是将Z 轴转向了上方(即显示器的高),而此时场中如还有其它的模型的话,它们的坐标未受影响,仍是 Java 3D 的坐标系。通常情况下我们载入的模型大小并不是我们所要的,我们必
10、须要在场景中对模型进行缩放操作。Java 3D 中对模型进行缩放需要用到 Transform3D 的setScale 方法,方法可以接收一个 double 值或一个 Vector3d 对象的实例,当使用 double 值做参数时模型将在 XYZ 轴上使用同样的比例因子进行缩放,而Vector3d 实例则可以分别为 XYZ 轴指定不同的比例因子,比例因子越接近 0,模型就越小,当设为 0 时模型即小的不可见了。t3d.setScale(0.05d);或t3d.setScale(new Vector3d(0.01d,0,02d,0.03d);好了,现在我将代码整理如下:Gamemain.java
11、程序主入口ScreenManager.java 窗口框架类LoadModelDemo.java 演示载入一个外部 3D 模型文件ColourTile.java 实现一个平面用于地面中的单块地砖CheckedFloor.java 实现场景中的地面GameMain.javaview plaincopy to clipboardprint?import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import javax.swing.JOptionPane; i
12、mport javax.swing.JPanel; public class GameMain private static int scrWidth = 800; private static int scrHeight = 600; private static int scrBitdepth = 32; private JFrame gameFrame; private JPanel gamePanel; public static void main(String args) GameMain game = new GameMain(); public GameMain() Scree
13、nManager screen = new ScreenManager(scrWidth,scrHeight,scrBitdepth,“Java 3D Test“); screen.setWindowMode(); gameFrame = screen.getFrame(); gamePanel = new LoadModelDemo(scrWidth,scrHeight); gameFrame.add(gamePanel); import java.awt.event.WindowAdapter;import java.awt.event.WindowEvent;import javax.s
14、wing.JFrame;import javax.swing.JOptionPane;import javax.swing.JPanel;public class GameMain private static int scrWidth = 800;private static int scrHeight = 600;private static int scrBitdepth = 32;private JFrame gameFrame;private JPanel gamePanel;public static void main(String args)GameMain game = ne
15、w GameMain();public GameMain()ScreenManager screen = new ScreenManager(scrWidth,scrHeight,scrBitdepth,“Java 3D Test“);screen.setWindowMode();gameFrame = screen.getFrame();gamePanel = new LoadModelDemo(scrWidth,scrHeight);gameFrame.add(gamePanel);ScreenManager.javaview plaincopy to clipboardprint?imp
16、ort java.awt.Dimension; import java.awt.DisplayMode; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import javax.swing.JOptionPane
17、; public class ScreenManager private GraphicsDevice device; private JFrame frame; private String title; private boolean isResizable; private boolean isWindowMode; private int scrWidth; private int scrHeight; private int scrBitdepth; public ScreenManager(int scrWidth,int scrHeight,int scrBitdepth,Str
18、ing title) this.scrWidth = scrWidth; this.scrHeight = scrHeight; this.scrBitdepth = scrBitdepth; this.title = title; public ScreenManager(String title) this.title = title; this.frame.setTitle(title); public void setFullScreenMode() device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaul
19、tScreenDevice(); if(isSupportDisplayMode(scrWidth,scrHeight,scrBitdepth) frame = new JFrame(); frame.setUndecorated(true); frame.setResizable(false); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.setVisible(true); device.setFullScreenWindow(frame); try if(device.isFullScreenSuppo
20、rted() catch(IllegalArgumentException e) e.printStackTrace(); System.exit(0); else JOptionPane.showMessageDialog(null, “不支持的显示分辨率!“,“错误“,JOptionPane.ERROR_MESSAGE); System.exit(0); private boolean isSupportDisplayMode(int width,int height,int bitdepth) DisplayMode modes = device.getDisplayModes(); f
21、or(DisplayMode mode : modes) if(mode.getWidth()=width return false; public void setWindowMode() frame = new JFrame(); frame.setResizable(false);/禁止窗体改变大小 frame.setPreferredSize(new Dimension(scrWidth,scrHeight); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);/响应窗体的关闭事件,但不关闭窗体 frame.setVi
22、sible(true); / 侦听窗体事件并捕获窗体关闭中的事件,在用户确认后退出程序 frame.addWindowListener(new WindowAdapter() public void windowClosing(WindowEvent e) int res = JOptionPane.showConfirmDialog(null, “是否退出!“,“退出“,JOptionPane.YES_NO_OPTION); if(res = JOptionPane.YES_OPTION) closeFrame(); ); this.setFrametoCenter(); public vo
23、id setFullWindowMode() if(frame != null) device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); DisplayMode displayMode = device.getDisplayMode(); frame.setPreferredSize(new Dimension(displayMode.getWidth(),displayMode.getHeight(); public int getWidth() return scrWidth;
24、 public int getHeight() return scrHeight; public JFrame getFrame() return frame; / 将窗体在显示屏幕内居中显示 public void setFrametoCenter() if(device!=null) return; Insets inset = frame.getInsets(); int scrx=0; int scry=0; Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize(); if(scrSize.width scrWidt
25、h) scrx = (scrSize.width-scrWidth)/2; if(scrSize.height scrHeight) scry = (scrSize.height-scrHeight)/2; frame.setBounds(scrx-inset.left, scry-inset.top, scrWidth+inset.right+inset.left, scrHeight+inset.bottom+inset.top); / 关闭窗体事件 public void closeFrame() frame.dispose(); System.exit(0); import java.
26、awt.Dimension;import java.awt.DisplayMode;import java.awt.GraphicsDevice;import java.awt.GraphicsEnvironment;import java.awt.Insets;import java.awt.Toolkit;import java.awt.event.WindowAdapter;import java.awt.event.WindowEvent;import javax.swing.JFrame;import javax.swing.JOptionPane;public class Scre
27、enManager private GraphicsDevice device;private JFrame frame;private String title;private boolean isResizable;private boolean isWindowMode;private int scrWidth;private int scrHeight;private int scrBitdepth;public ScreenManager(int scrWidth,int scrHeight,int scrBitdepth,String title)this.scrWidth = s
28、crWidth;this.scrHeight = scrHeight;this.scrBitdepth = scrBitdepth;this.title = title;public ScreenManager(String title)this.title = title;this.frame.setTitle(title);public void setFullScreenMode()device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();if(isSupportDisplayM
29、ode(scrWidth,scrHeight,scrBitdepth)frame = new JFrame();frame.setUndecorated(true);frame.setResizable(false);frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);frame.setVisible(true);device.setFullScreenWindow(frame);tryif(device.isFullScreenSupported()catch(IllegalArgumentException e)e.prin
30、tStackTrace();System.exit(0);elseJOptionPane.showMessageDialog(null, “不支持的显示分辨率!“,“错误“,JOptionPane.ERROR_MESSAGE);System.exit(0);private boolean isSupportDisplayMode(int width,int height,int bitdepth)DisplayMode modes = device.getDisplayModes();for(DisplayMode mode : modes)if(mode.getWidth()=width r
31、eturn false;public void setWindowMode()frame = new JFrame();frame.setResizable(false);/禁止窗体改变大小frame.setPreferredSize(new Dimension(scrWidth,scrHeight);frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);/响应窗体的关闭事件,但不关闭窗体frame.setVisible(true);/ 侦听窗体事件并捕获窗体关闭中的事件,在用户确认后退出程序frame.addWindowList
32、ener(new WindowAdapter()public void windowClosing(WindowEvent e)int res = JOptionPane.showConfirmDialog(null, “是否退出!“,“退出“,JOptionPane.YES_NO_OPTION);if(res = JOptionPane.YES_OPTION)closeFrame(););this.setFrametoCenter();public void setFullWindowMode()if(frame != null)device = GraphicsEnvironment.ge
33、tLocalGraphicsEnvironment().getDefaultScreenDevice();DisplayMode displayMode = device.getDisplayMode();frame.setPreferredSize(new Dimension(displayMode.getWidth(),displayMode.getHeight();public int getWidth()return scrWidth;public int getHeight()return scrHeight;public JFrame getFrame()return frame;
34、/ 将窗体在显示屏幕内居中显示public void setFrametoCenter()if(device!=null)return;Insets inset = frame.getInsets();int scrx=0;int scry=0;Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize();if(scrSize.width scrWidth)scrx = (scrSize.width-scrWidth)/2;if(scrSize.height scrHeight)scry = (scrSize.height-sc
35、rHeight)/2;frame.setBounds(scrx-inset.left, scry-inset.top, scrWidth+inset.right+inset.left, scrHeight+inset.bottom+inset.top);/ 关闭窗体事件public void closeFrame()frame.dispose();System.exit(0);LoadModelDemo.javaview plaincopy to clipboardprint?import java.awt.BorderLayout; import java.awt.Dimension; im
36、port java.awt.Font; import java.awt.GraphicsConfiguration; import java.io.FileNotFoundException; import java.util.Enumeration; import javax.media.j3d.Background; import javax.media.j3d.BoundingBox; import javax.media.j3d.BoundingSphere; import javax.media.j3d.BranchGroup; import javax.media.j3d.Canv
37、as3D; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.swing.JPanel; import javax.vecmath.Color3f; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; import com.sun.j3d.loaders.IncorrectFormatException; import com.sun.j3
38、d.loaders.ParsingErrorException; import com.sun.j3d.loaders.Scene; import com.sun.j3d.loaders.objectfile.ObjectFile; import com.sun.j3d.utils.behaviors.vp.OrbitBehavior; import com.sun.j3d.utils.geometry.Text2D; import com.sun.j3d.utils.universe.SimpleUniverse; import com.sun.j3d.utils.universe.View
39、ingPlatform; public class LoadModelDemo extends JPanel private BranchGroup sceneBG; private SimpleUniverse universe; private BoundingSphere bounds; private double boundRadius = 100; public LoadModelDemo(int width,int height) this.setLayout(new BorderLayout(); GraphicsConfiguration config = SimpleUni
40、verse.getPreferredConfiguration(); Canvas3D canvas = new Canvas3D(config); canvas.setSize(width, height); this.add(canvas,BorderLayout.CENTER); universe = new SimpleUniverse(canvas); createSceneGroup(); initUserPosition(); orbitControls(canvas); universe.addBranchGraph(sceneBG); public void createSc
41、eneGroup() sceneBG = new BranchGroup(); bounds = new BoundingSphere(new Point3d(0,0,0),boundRadius); addBackground(); TransformGroup objModel = new TransformGroup4; sceneBG.addChild(objModel0 = loadModel(“leet/Liit.obj“,ObjectFile.RESIZE,new Vector3d(-5.4,0,0); sceneBG.addChild(objModel1 = loadModel
42、(“leet/Liit.obj“,ObjectFile.REVERSE,new Vector3d(-1.8,0,0); sceneBG.addChild(objModel2 = loadModel(“leet/Liit.obj“,ObjectFile.TRIANGULATE,new Vector3d(1.8,0,0); sceneBG.addChild(objModel3 = loadModel(“leet/Liit.obj“,ObjectFile.STRIPIFY,new Vector3d(5.4,0,0); sceneBG.addChild(new CheckerFloor().getBG
43、(); /对 sceneBG 有关的对象进行编译和缓存,如果在编译之后再次添加其它分支的话将抛异常 sceneBG.compile(); public void addBackground() Background back = new Background(); back.setApplicationBounds(bounds); back.setColor(0.17f, 0.62f, 0.92f); sceneBG.addChild(back); private void initUserPosition() /返回当前虚拟世界的观察平台 ViewingPlatform vp = univ
44、erse.getViewingPlatform(); /得到观察平台的坐标枝花点 TransformGroup steerTG = vp.getViewPlatformTransform(); Transform3D t3d = new Transform3D(); steerTG.getTransform(t3d); /设置观察点坐标在(0,5,20),看向坐标(0,0,0)处,指定 Y 轴向正数沿伸方向为正方向 t3d.lookAt(new Point3d(0,5,20), new Point3d(0,0,0), new Vector3d(0,1,0); t3d.invert(); ste
45、erTG.setTransform(t3d); private void orbitControls(Canvas3D canvas) OrbitBehavior orbit = new OrbitBehavior(canvas, OrbitBehavior.REVERSE_ALL); orbit.setSchedulingBounds(bounds); ViewingPlatform vp = universe.getViewingPlatform( ); vp.setViewPlatformBehavior(orbit); private TransformGroup loadModel(
46、String filename,int flag,Vector3d translation) Scene loadScene = this.loadFromFile(filename,flag); Transform3D t3d = new Transform3D(); t3d = this.rotateModel(); t3d.setScale(this.calcScaleFactor(loadScene.getSceneGroup(); t3d.setTranslation(translation); TransformGroup tg = new TransformGroup(t3d);
47、 tg.addChild(loadScene.getSceneGroup(); return tg; private Scene loadFromFile(String filename,int flag) Scene loadScene = null; ObjectFile obj = new ObjectFile(flag); try loadScene = obj.load(this.getClass().getClassLoader().getResource(filename); catch (FileNotFoundException e) / TODO Auto-generate
48、d catch block System.out.println(“文件未找到或文件路径不正确 “); e.printStackTrace(); catch (IncorrectFormatException e) / TODO Auto-generated catch block System.out.println(“文件格式不正确 “); e.printStackTrace(); catch (ParsingErrorException e) / TODO Auto-generated catch block System.out.println(“装载器解析文件时出错 “); e.printStackTrace(); finally return loadScene;