Skip to content

Commit cb73f1e

Browse files
committed
OBJLoader enhancement: named groups support
1 parent 8aa50e9 commit cb73f1e

11 files changed

Lines changed: 458 additions & 29 deletions

File tree

jme3-core/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ sourceSets {
1717
}
1818
}
1919

20+
dependencies {
21+
testCompile project(':jme3-testdata')
22+
}
23+
2024
task updateVersionPropertiesFile {
2125
def versionFile = file('src/main/resources/com/jme3/system/version.properties')
2226
def versionFileText = "# THIS IS AN AUTO-GENERATED FILE..\n" +

jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ protected void createMaterial(){
152152
material.setFloat("AlphaDiscardThreshold", 0.01f);
153153
}
154154

155+
material.setName(matName);
155156
matList.put(matName, material);
156157
}
157158

jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,9 @@ public final class OBJLoader implements AssetLoader {
6666
protected final ArrayList<Vector3f> verts = new ArrayList<Vector3f>();
6767
protected final ArrayList<Vector2f> texCoords = new ArrayList<Vector2f>();
6868
protected final ArrayList<Vector3f> norms = new ArrayList<Vector3f>();
69-
70-
protected final ArrayList<Face> faces = new ArrayList<Face>();
71-
protected final HashMap<String, ArrayList<Face>> matFaces = new HashMap<String, ArrayList<Face>>();
72-
69+
70+
private final ArrayList<Group> groups = new ArrayList<Group>();
71+
7372
protected String currentMatName;
7473
protected String currentObjectName;
7574

@@ -87,6 +86,16 @@ public final class OBJLoader implements AssetLoader {
8786
protected String objName;
8887
protected Node objNode;
8988

89+
private static class Group {
90+
private String name;
91+
private final ArrayList<Face> faces = new ArrayList<Face>();
92+
private final HashMap<String, ArrayList<Face>> matFaces = new HashMap<String, ArrayList<Face>>();
93+
94+
public Group(final String name) {
95+
this.name = name;
96+
}
97+
}
98+
9099
protected static class Vertex {
91100

92101
Vector3f v;
@@ -164,8 +173,7 @@ public void reset(){
164173
verts.clear();
165174
texCoords.clear();
166175
norms.clear();
167-
faces.clear();
168-
matFaces.clear();
176+
groups.clear();
169177

170178
vertIndexMap.clear();
171179
indexVertMap.clear();
@@ -289,10 +297,17 @@ protected void readFace(){
289297
f.verticies[i] = vertList.get(i);
290298
}
291299

292-
if (matList != null && matFaces.containsKey(currentMatName)){
293-
matFaces.get(currentMatName).add(f);
300+
Group group = groups.get(groups.size() - 1);
301+
302+
if (currentMatName != null && matList != null && matList.containsKey(currentMatName)){
303+
ArrayList<Face> matFaces = group.matFaces.get(currentMatName);
304+
if (matFaces == null) {
305+
matFaces = new ArrayList<Face>();
306+
group.matFaces.put(currentMatName, matFaces);
307+
}
308+
matFaces.add(f);
294309
}else{
295-
faces.add(f); // faces that belong to the default material
310+
group.faces.add(f); // faces that belong to the default material
296311
}
297312
}
298313

@@ -337,13 +352,6 @@ protected void loadMtlLib(String name) throws IOException{
337352
} catch (AssetNotFoundException ex){
338353
logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{name, key});
339354
}
340-
341-
if (matList != null){
342-
// create face lists for every material
343-
for (String matName : matList.keySet()){
344-
matFaces.put(matName, new ArrayList<Face>());
345-
}
346-
}
347355
}
348356

349357
protected boolean nextStatement(){
@@ -387,8 +395,14 @@ protected boolean readLine() throws IOException{
387395
// specify MTL lib to use for this OBJ file
388396
String mtllib = scan.nextLine().trim();
389397
loadMtlLib(mtllib);
390-
}else if (cmd.equals("s") || cmd.equals("g")){
398+
}else if (cmd.equals("s")) {
399+
logger.log(Level.WARNING, "smoothing groups are not supported, statement ignored: {0}", cmd);
400+
return nextStatement();
401+
}else if (cmd.equals("mg")) {
402+
logger.log(Level.WARNING, "merge groups are not supported, statement ignored: {0}", cmd);
391403
return nextStatement();
404+
}else if (cmd.equals("g")) {
405+
groups.add(new Group(scan.nextLine().trim()));
392406
}else{
393407
// skip entire command until next line
394408
logger.log(Level.WARNING, "Unknown statement in OBJ! {0}", cmd);
@@ -565,11 +579,14 @@ public Object load(AssetInfo info) throws IOException{
565579
}
566580

567581
objNode = new Node(objName + "-objnode");
582+
583+
Group defaultGroupStub = new Group(null);
584+
groups.add(defaultGroupStub);
568585

569586
if (!(info.getKey() instanceof ModelKey))
570587
throw new IllegalArgumentException("Model assets must be loaded using a ModelKey");
571588

572-
InputStream in = null;
589+
InputStream in = null;
573590
try {
574591
in = info.openStream();
575592

@@ -583,25 +600,43 @@ public Object load(AssetInfo info) throws IOException{
583600
}
584601
}
585602

586-
if (matFaces.size() > 0){
587-
for (Entry<String, ArrayList<Face>> entry : matFaces.entrySet()){
588-
ArrayList<Face> materialFaces = entry.getValue();
589-
if (materialFaces.size() > 0){
590-
Geometry geom = createGeometry(materialFaces, entry.getKey());
603+
for (Group group : groups) {
604+
if (group == defaultGroupStub) {
605+
materializeGroup(group, objNode);
606+
} else {
607+
Node groupNode = new Node(group.name);
608+
materializeGroup(group, groupNode);
609+
if (groupNode.getQuantity() == 1) {
610+
Spatial geom = groupNode.getChild(0);
611+
geom.setName(groupNode.getName());
591612
objNode.attachChild(geom);
613+
} else if (groupNode.getQuantity() > 1) {
614+
objNode.attachChild(groupNode);
592615
}
593616
}
594-
}else if (faces.size() > 0){
595-
// generate final geometry
596-
Geometry geom = createGeometry(faces, null);
597-
objNode.attachChild(geom);
598617
}
599618

600619
if (objNode.getQuantity() == 1)
601620
// only 1 geometry, so no need to send node
602-
return objNode.getChild(0);
621+
return objNode.getChild(0);
603622
else
604623
return objNode;
605624
}
606-
625+
626+
private void materializeGroup(Group group, Node container) throws IOException {
627+
if (group.matFaces.size() > 0) {
628+
for (Entry<String, ArrayList<Face>> entry : group.matFaces.entrySet()){
629+
ArrayList<Face> materialFaces = entry.getValue();
630+
if (materialFaces.size() > 0){
631+
Geometry geom = createGeometry(materialFaces, entry.getKey());
632+
container.attachChild(geom);
633+
}
634+
}
635+
} else if (group.faces.size() > 0) {
636+
// generate final geometry
637+
Geometry geom = createGeometry(group.faces, null);
638+
container.attachChild(geom);
639+
}
640+
}
641+
607642
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.jme3.scene.plugins;
2+
3+
import com.jme3.asset.AssetInfo;
4+
import com.jme3.asset.AssetLoader;
5+
import com.jme3.asset.AssetManager;
6+
import com.jme3.asset.ModelKey;
7+
import com.jme3.scene.Geometry;
8+
import com.jme3.scene.Node;
9+
import com.jme3.scene.Spatial;
10+
import com.jme3.system.TestUtil;
11+
import com.jme3.texture.Image;
12+
import org.junit.Before;
13+
import org.junit.Test;
14+
15+
import static org.junit.Assert.assertEquals;
16+
17+
public class OBJLoaderTest {
18+
private AssetManager assetManager;
19+
20+
@Before
21+
public void init() {
22+
assetManager = TestUtil.createAssetManager();
23+
// texture loaders are outside of core, so creating stub
24+
assetManager.registerLoader(PngLoaderStub.class, "png");
25+
26+
}
27+
28+
@Test
29+
public void testHappyPath() {
30+
Node scene = (Node) assetManager.loadModel(new ModelKey("OBJLoaderTest/TwoChairs.obj"));
31+
String sceneAsString = toDiffFriendlyString("", scene);
32+
System.out.println(sceneAsString);
33+
String expectedText = "" +
34+
// generated root name (as before named groups support)
35+
"TwoChairs-objnode\n" +
36+
// unnamed geometry with generated name (as before named groups support).
37+
// actually it's partially smoothed, but this fact is ignored.
38+
" TwoChairs-geom-0 (material: dot_purple)\n" +
39+
// named group as Geometry
40+
" Chair 2 (material: dot_purple)\n" +
41+
// named group as Geometry
42+
" Pillow 2 (material: dot_red)\n" +
43+
// named group as node with two dufferent Geometry instances,
44+
// because two materials are used (as before named groups support)
45+
" Podium\n" +
46+
" TwoChairs-geom-3 (material: dot_red)\n" +
47+
" TwoChairs-geom-4 (material: dot_blue)\n" +
48+
// named group as Geometry
49+
" Pillow 1 (material: dot_green)";
50+
assertEquals(expectedText, sceneAsString.trim());
51+
}
52+
53+
private static String toDiffFriendlyString(String indent, Spatial spatial) {
54+
if (spatial instanceof Geometry) {
55+
return indent + spatial.getName() + " (material: "+((Geometry) spatial).getMaterial().getName()+")\n";
56+
}
57+
if (spatial instanceof Node) {
58+
StringBuilder s = new StringBuilder();
59+
s.append(indent).append(spatial.getName()).append("\n");
60+
Node node = (Node) spatial;
61+
for (final Spatial child : node.getChildren()) {
62+
s.append(toDiffFriendlyString(indent + " ", child));
63+
}
64+
return s.toString();
65+
}
66+
return indent + spatial + "\n";
67+
}
68+
69+
public static class PngLoaderStub implements AssetLoader {
70+
@Override
71+
public Object load(final AssetInfo assetInfo) {
72+
return new Image();
73+
}
74+
}
75+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package jme3test.model;
2+
3+
import com.jme3.app.SimpleApplication;
4+
import com.jme3.asset.ModelKey;
5+
import com.jme3.collision.CollisionResult;
6+
import com.jme3.collision.CollisionResults;
7+
import com.jme3.font.BitmapFont;
8+
import com.jme3.font.BitmapText;
9+
import com.jme3.font.Rectangle;
10+
import com.jme3.light.AmbientLight;
11+
import com.jme3.math.ColorRGBA;
12+
import com.jme3.math.Ray;
13+
import com.jme3.math.Vector3f;
14+
import com.jme3.scene.Spatial;
15+
16+
public class TestObjGroupsLoading extends SimpleApplication {
17+
18+
public static void main(String[] args) {
19+
TestObjGroupsLoading app = new TestObjGroupsLoading();
20+
app.start();
21+
}
22+
23+
private BitmapText pointerDisplay;
24+
25+
@Override
26+
public void simpleInitApp() {
27+
28+
// load scene with following structure:
29+
// Chair 1 (just mesh without name) and named groups: Chair 2, Pillow 2, Podium
30+
Spatial scene = assetManager.loadModel(new ModelKey("OBJLoaderTest/TwoChairs.obj"));
31+
// add light to make it visible
32+
scene.addLight(new AmbientLight(ColorRGBA.White));
33+
// attach scene to the root
34+
rootNode.attachChild(scene);
35+
36+
// configure camera for best scene viewing
37+
cam.setLocation(new Vector3f(-3, 4, 3));
38+
cam.lookAtDirection(new Vector3f(0, -0.5f, -1), Vector3f.UNIT_Y);
39+
flyCam.setMoveSpeed(10);
40+
41+
// create display to indicate pointed geometry name
42+
pointerDisplay = new BitmapText(guiFont);
43+
pointerDisplay.setBox(new Rectangle(0, settings.getHeight(), settings.getWidth(), settings.getHeight()));
44+
pointerDisplay.setAlignment(BitmapFont.Align.Center);
45+
pointerDisplay.setVerticalAlignment(BitmapFont.VAlign.Center);
46+
guiNode.attachChild(pointerDisplay);
47+
}
48+
49+
@Override
50+
public void simpleUpdate(final float tpf) {
51+
52+
// ray to the center of the screen from the camera
53+
Ray ray = new Ray(cam.getLocation(), cam.getDirection());
54+
55+
// find object at the center of the screen
56+
57+
final CollisionResults results = new CollisionResults();
58+
rootNode.collideWith(ray, results);
59+
60+
CollisionResult result = results.getClosestCollision();
61+
if (result == null) {
62+
pointerDisplay.setText("");
63+
} else {
64+
// display pointed geometry and it's parents names
65+
StringBuilder sb = new StringBuilder();
66+
for (Spatial node = result.getGeometry(); node != null; node = node.getParent()) {
67+
if (sb.length() > 0) {
68+
sb.append(" < ");
69+
}
70+
sb.append(node.getName());
71+
}
72+
pointerDisplay.setText(sb);
73+
}
74+
}
75+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
newmtl dot_purple
2+
map_Kd -o 0 0 -s 1 1 dot_purple.png
3+
Kd 1 1 1
4+
d 1
5+
6+
newmtl dot_red
7+
map_Kd -o 0 0 -s 1 1 dot_red.png
8+
Kd 1 1 1
9+
d 1
10+
11+
newmtl dot_blue
12+
map_Kd -o 0 0 -s 1 1 dot_blue.png
13+
Kd 1 1 1
14+
d 1
15+
16+
newmtl dot_green
17+
map_Kd -o 0 0 -s 1 1 dot_green.png
18+
Kd 1 1 1
19+
d 1
20+

0 commit comments

Comments
 (0)