|
1 | 1 | /* |
2 | | - * Copyright (c) 2009-2021 jMonkeyEngine |
| 2 | + * Copyright (c) 2009-2025 jMonkeyEngine |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
|
29 | 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | 31 | */ |
32 | | - |
33 | 32 | package jme3test.collision; |
34 | 33 |
|
35 | 34 | import com.jme3.app.SimpleApplication; |
36 | 35 | import com.jme3.collision.CollisionResult; |
37 | 36 | import com.jme3.collision.CollisionResults; |
| 37 | +import com.jme3.font.BitmapText; |
38 | 38 | import com.jme3.light.DirectionalLight; |
39 | 39 | import com.jme3.material.Material; |
| 40 | +import com.jme3.material.Materials; |
40 | 41 | import com.jme3.math.ColorRGBA; |
| 42 | +import com.jme3.math.FastMath; |
41 | 43 | import com.jme3.math.Quaternion; |
42 | 44 | import com.jme3.math.Ray; |
43 | 45 | import com.jme3.math.Vector3f; |
| 46 | +import com.jme3.post.FilterPostProcessor; |
| 47 | +import com.jme3.renderer.queue.RenderQueue; |
44 | 48 | import com.jme3.scene.Geometry; |
| 49 | +import com.jme3.scene.Mesh; |
45 | 50 | import com.jme3.scene.Node; |
46 | 51 | import com.jme3.scene.Spatial; |
47 | 52 | import com.jme3.scene.debug.Arrow; |
48 | 53 | import com.jme3.scene.shape.Box; |
| 54 | +import com.jme3.scene.shape.Cylinder; |
| 55 | +import com.jme3.scene.shape.Sphere; |
| 56 | +import com.jme3.scene.shape.Torus; |
| 57 | +import com.jme3.shadow.DirectionalLightShadowFilter; |
| 58 | +import com.jme3.shadow.EdgeFilteringMode; |
49 | 59 |
|
| 60 | +/** |
| 61 | + * The primary purpose of TestMousePick is to illustrate how to detect intersections |
| 62 | + * between a ray (originating from the camera's cursor position) and 3D objects in the scene. |
| 63 | + * <p> |
| 64 | + * When an intersection occurs, a visual marker (a red arrow) |
| 65 | + * is placed at the collision point, and the name of the |
| 66 | + * intersected object is displayed on the HUD. |
| 67 | + * |
| 68 | + * @author capdevon |
| 69 | + */ |
50 | 70 | public class TestMousePick extends SimpleApplication { |
51 | 71 |
|
52 | 72 | public static void main(String[] args) { |
53 | 73 | TestMousePick app = new TestMousePick(); |
54 | 74 | app.start(); |
55 | 75 | } |
56 | | - |
| 76 | + |
| 77 | + private BitmapText hud; |
57 | 78 | private Node shootables; |
58 | 79 | private Geometry mark; |
59 | 80 |
|
60 | 81 | @Override |
61 | 82 | public void simpleInitApp() { |
62 | | - flyCam.setEnabled(false); |
| 83 | + hud = createLabel(10, 10, "Text"); |
| 84 | + configureCamera(); |
63 | 85 | initMark(); |
| 86 | + setupScene(); |
| 87 | + setupLights(); |
| 88 | + } |
| 89 | + |
| 90 | + private void configureCamera() { |
| 91 | + flyCam.setMoveSpeed(15f); |
| 92 | + flyCam.setDragToRotate(true); |
64 | 93 |
|
| 94 | + cam.setLocation(Vector3f.UNIT_XYZ.mult(6)); |
| 95 | + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); |
| 96 | + } |
| 97 | + |
| 98 | + private void setupScene() { |
65 | 99 | /* Create four colored boxes and a floor to shoot at: */ |
66 | 100 | shootables = new Node("Shootables"); |
| 101 | + shootables.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); |
67 | 102 | rootNode.attachChild(shootables); |
68 | | - shootables.attachChild(makeCube("a Dragon", -2f, 0f, 1f)); |
69 | | - shootables.attachChild(makeCube("a tin can", 1f, -2f, 0f)); |
70 | | - shootables.attachChild(makeCube("the Sheriff", 0f, 1f, -2f)); |
71 | | - shootables.attachChild(makeCube("the Deputy", 1f, 0f, -4f)); |
72 | | - shootables.attachChild(makeFloor()); |
73 | | - shootables.attachChild(makeCharacter()); |
| 103 | + |
| 104 | + Geometry sphere = makeShape("Sphere", new Sphere(32, 32, 1f), ColorRGBA.randomColor()); |
| 105 | + sphere.setLocalTranslation(-2f, 0f, 1f); |
| 106 | + shootables.attachChild(sphere); |
| 107 | + |
| 108 | + Geometry box = makeShape("Box", new Box(1, 1, 1), ColorRGBA.randomColor()); |
| 109 | + box.setLocalTranslation(1f, -2f, 0f); |
| 110 | + shootables.attachChild(box); |
| 111 | + |
| 112 | + Geometry cylinder = makeShape("Cylinder", new Cylinder(16, 16, 1.0f, 1.0f, true), ColorRGBA.randomColor()); |
| 113 | + cylinder.setLocalTranslation(0f, 1f, -2f); |
| 114 | + cylinder.rotate(90 * FastMath.DEG_TO_RAD, 0, 0); |
| 115 | + shootables.attachChild(cylinder); |
| 116 | + |
| 117 | + Geometry torus = makeShape("Torus", new Torus(16, 16, 0.15f, 0.5f), ColorRGBA.randomColor()); |
| 118 | + torus.setLocalTranslation(1f, 0f, -4f); |
| 119 | + shootables.attachChild(torus); |
| 120 | + |
| 121 | + // load a character from jme3-testdata |
| 122 | + Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); |
| 123 | + golem.scale(0.5f); |
| 124 | + golem.setLocalTranslation(-1.0f, -1.5f, -0.6f); |
| 125 | + shootables.attachChild(golem); |
| 126 | + |
| 127 | + Geometry floor = makeShape("Floor", new Box(15, .2f, 15), ColorRGBA.Gray); |
| 128 | + floor.setLocalTranslation(0, -4, -5); |
| 129 | + shootables.attachChild(floor); |
| 130 | + } |
| 131 | + |
| 132 | + private void setupLights() { |
| 133 | + // We must add a light to make the model visible |
| 134 | + DirectionalLight sun = new DirectionalLight(); |
| 135 | + sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal()); |
| 136 | + rootNode.addLight(sun); |
| 137 | + |
| 138 | + // init shadows |
| 139 | + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); |
| 140 | + DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 2048, 3); |
| 141 | + dlsf.setLight(sun); |
| 142 | + dlsf.setLambda(0.55f); |
| 143 | + dlsf.setShadowIntensity(0.8f); |
| 144 | + dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); |
| 145 | + fpp.addFilter(dlsf); |
| 146 | + viewPort.addProcessor(fpp); |
74 | 147 | } |
75 | 148 |
|
| 149 | + private final CollisionResults results = new CollisionResults(); |
| 150 | + private final Quaternion tempQuat = new Quaternion(); |
| 151 | + |
76 | 152 | @Override |
77 | 153 | public void simpleUpdate(float tpf){ |
78 | | - Vector3f origin = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.0f); |
79 | | - Vector3f direction = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f); |
80 | | - direction.subtractLocal(origin).normalizeLocal(); |
81 | 154 |
|
82 | | - Ray ray = new Ray(origin, direction); |
83 | | - CollisionResults results = new CollisionResults(); |
| 155 | + Ray ray = cam.screenPointToRay(inputManager.getCursorPosition()); |
| 156 | + results.clear(); |
84 | 157 | shootables.collideWith(ray, results); |
85 | | -// System.out.println("----- Collisions? " + results.size() + "-----"); |
86 | | -// for (int i = 0; i < results.size(); i++) { |
87 | | -// // For each hit, we know distance, impact point, name of geometry. |
88 | | -// float dist = results.getCollision(i).getDistance(); |
89 | | -// Vector3f pt = results.getCollision(i).getWorldContactPoint(); |
90 | | -// String hit = results.getCollision(i).getGeometry().getName(); |
91 | | -// System.out.println("* Collision #" + i); |
92 | | -// System.out.println(" You shot " + hit + " at " + pt + ", " + dist + " wu away."); |
93 | | -// } |
| 158 | + |
94 | 159 | if (results.size() > 0) { |
95 | 160 | CollisionResult closest = results.getClosestCollision(); |
96 | | - mark.setLocalTranslation(closest.getContactPoint()); |
| 161 | + Vector3f point = closest.getContactPoint(); |
| 162 | + Vector3f normal = closest.getContactNormal(); |
97 | 163 |
|
98 | | - Quaternion q = new Quaternion(); |
99 | | - q.lookAt(closest.getContactNormal(), Vector3f.UNIT_Y); |
100 | | - mark.setLocalRotation(q); |
| 164 | + tempQuat.lookAt(normal, Vector3f.UNIT_Y); |
| 165 | + mark.setLocalRotation(tempQuat); |
| 166 | + mark.setLocalTranslation(point); |
101 | 167 |
|
102 | 168 | rootNode.attachChild(mark); |
| 169 | + hud.setText(closest.getGeometry().toString()); |
| 170 | + |
103 | 171 | } else { |
| 172 | + hud.setText("No collision"); |
104 | 173 | rootNode.detachChild(mark); |
105 | 174 | } |
106 | 175 | } |
107 | | - |
108 | | - /** A cube object for target practice */ |
109 | | - private Geometry makeCube(String name, float x, float y, float z) { |
110 | | - Box box = new Box(1, 1, 1); |
111 | | - Geometry cube = new Geometry(name, box); |
112 | | - cube.setLocalTranslation(x, y, z); |
113 | | - Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
114 | | - mat1.setColor("Color", ColorRGBA.randomColor()); |
115 | | - cube.setMaterial(mat1); |
116 | | - return cube; |
| 176 | + |
| 177 | + private BitmapText createLabel(int x, int y, String text) { |
| 178 | + BitmapText bmp = guiFont.createLabel(text); |
| 179 | + bmp.setLocalTranslation(x, settings.getHeight() - y, 0); |
| 180 | + bmp.setColor(ColorRGBA.Red); |
| 181 | + guiNode.attachChild(bmp); |
| 182 | + return bmp; |
117 | 183 | } |
118 | 184 |
|
119 | | - /** A floor to show that the "shot" can go through several objects. */ |
120 | | - private Geometry makeFloor() { |
121 | | - Box box = new Box(15, .2f, 15); |
122 | | - Geometry floor = new Geometry("the Floor", box); |
123 | | - floor.setLocalTranslation(0, -4, -5); |
124 | | - Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
125 | | - mat1.setColor("Color", ColorRGBA.Gray); |
126 | | - floor.setMaterial(mat1); |
127 | | - return floor; |
| 185 | + private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) { |
| 186 | + Geometry geo = new Geometry(name, mesh); |
| 187 | + Material mat = new Material(assetManager, Materials.LIGHTING); |
| 188 | + mat.setBoolean("UseMaterialColors", true); |
| 189 | + mat.setColor("Diffuse", color); |
| 190 | + geo.setMaterial(mat); |
| 191 | + return geo; |
128 | 192 | } |
129 | 193 |
|
130 | 194 | /** |
131 | 195 | * A red arrow to mark the spot being picked. |
132 | 196 | */ |
133 | 197 | private void initMark() { |
134 | 198 | Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f)); |
135 | | - mark = new Geometry("BOOM!", arrow); |
136 | | - Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
137 | | - mark_mat.setColor("Color", ColorRGBA.Red); |
138 | | - mark.setMaterial(mark_mat); |
| 199 | + mark = new Geometry("Marker", arrow); |
| 200 | + Material mat = new Material(assetManager, Materials.UNSHADED); |
| 201 | + mat.setColor("Color", ColorRGBA.Red); |
| 202 | + mark.setMaterial(mat); |
139 | 203 | } |
140 | 204 |
|
141 | | - private Spatial makeCharacter() { |
142 | | - // load a character from jme3-testdata |
143 | | - Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); |
144 | | - golem.scale(0.5f); |
145 | | - golem.setLocalTranslation(-1.0f, -1.5f, -0.6f); |
146 | | - |
147 | | - // We must add a light to make the model visible |
148 | | - DirectionalLight sun = new DirectionalLight(); |
149 | | - sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal()); |
150 | | - golem.addLight(sun); |
151 | | - return golem; |
152 | | - } |
153 | 205 | } |
0 commit comments