In the previous tutorials about min3D for Android we took a closer look on how to optimize the work flow from Blender or Daz3D to min3D. In today’s article you will learn how to easily change the texture of a 3D object stored in *.obj or *.3ds format. Although this sounds very easy while writing this tutorial, I’ve noticed numerous problems by loading these 3D objects within min3D especially if you are modeling with Blender. This tutorial should be considered as a technical proof of concept for changing the texture of a 3D object in min3D at runtime. You will find the necessary files for this tutorial at the end of the page. Feel free to use my 3D model of the squared robot for your own work if you want.
Final result
In order to give you a better overview of what we are trying to achieve, here are two screenshots. We are going to load with min3d a 3d model of a very famous green robot. When touching the rotating robot the texture will change.
Modeling with Blender
This part is pretty easy, model your object and unwrap, bake and light up the texture as you are used to do it in Blender. There is no need to modify your workflow for this part. Now it’s time to export your model, and here comes the interesting part. min3D accepts several file formats including 3DS and OBJ. We will focus on these last ones.
Export to 3DS
With 3DS you have to load the texture afterwards (from the Java code). Why? because we have texture names with names longer than 8 characters and those names can’t be resolved ( yes ! this restriction still exists in 2012!).
http://blenderartists.org/forum/showthread.php?217981-How-to-export-3ds-files-with-textures
Export to OBJ
Blender will export two files: the object file (*.obj) and the linked material file (*.mtl). But in order to be able to modify with min3D each part of our squared robot we must select the Objects as OBJ Objects option.
This option will add a line to each mtl entry in the squared_robot_mtl file with the name of the object (see following line)
o head_Cube.001
Without this option checked, you won’t be able to call the following function in min3D:
androidRobot3DObject.getChildByName("head")
Switch to Eclipse or to your favorite Android programming IDE. Now that you have your files exported (in the case of 3DS a single *.3ds file and for OBJ export two files) put them into the /raw directory (rename them, so there are no problems with the Android compiler) and put the textures into the /drawable folder
Note: If you are not very familiar with the structure and where to place your material files please read the first tutorial about min3D: http://www.mat-d.com/site/tutorial-load-a-3d-obj-model-with-min3d-for-android/
Create the parser that will load our 3d file
IParser myParser = Parser.createParser(Parser.Type.MAX_3DS, getResources(), "com.min3dtest:raw/squared_robot_3ds", false);
Now we are going to preload the textures into min3D with following lines:
Bitmap b; b = Utils.makeBitmapFromResourceId(R.drawable.squared_robot_body_business); Shared.textureManager().addTextureId(b, "squared_robot_body_business"); b.recycle(); b = Utils.makeBitmapFromResourceId(R.drawable.squared_robot_body); Shared.textureManager().addTextureId(b, "squared_robot_body"); b.recycle(); b = Utils.makeBitmapFromResourceId(R.drawable.squared_robot_arm); Shared.textureManager().addTextureId(b,"squared_robot_arm"); b.recycle(); b = Utils.makeBitmapFromResourceId(R.drawable.squared_robot_foot); Shared.textureManager().addTextureId(b,"squared_robot_foot"); b.recycle(); b = Utils.makeBitmapFromResourceId(R.drawable.squared_robot_antenna); Shared.textureManager().addTextureId(b,"squared_robot_antenna"); b.recycle(); b = Utils.makeBitmapFromResourceId(R.drawable.squared_robot_head); Shared.textureManager().addTextureId(b,"squared_robot_head"); b.recycle(); b = Utils.makeBitmapFromResourceId(R.drawable.squared_robot_head_business); Shared.textureManager().addTextureId(b,"squared_robot_head_business"); b.recycle();
And of course we must create a function that will load all the textures of our robot. The trick of loading the correct texture on the correct part of the object is made over the combination of two lines whereas addById contains the name of the texture id previously added over the Shared.textureManager().addTexturedId() function. At this stage once added over the Shared.textureManager a texture can be accessed and added to any child of a 3d object.
androidRobot3DObject.getChildAt(i).textures().clear(); androidRobot3DObject.getChildAt(i).textures().addById("squared_robot_body");
Here is the complete function of the manual loading of textures by accessing the children over the getChildAt() function. Notice that if you are not 100% completely sure how the name of the child is (perhaps the designer gave you a file in which the name’s string are complex e.g: robot_body_part_14582012), you can check them with indexOf(). That’s pretty easy and saves you a lot of time.
/** Updates child and their textures with the textures to be loaded */ public void loadAllTextures() { int numChildren = androidRobot3DObject.numChildren(); for(int i = 0;i<numChildren;i++) { String name = androidRobot3DObject.getChildAt(i).name(); Log.d("min3d", "Texture name: " + name); // The name is either extracted from the _mtl file // or directly from the *.3ds file // The name can be given directly into Blender if(name.indexOf("body")!=-1) { androidRobot3DObject.getChildAt(i).textures().clear(); androidRobot3DObject.getChildAt(i).textures().addById("squared_robot_body"); } if(name.indexOf("head")!=-1) { androidRobot3DObject.getChildAt(i).textures().clear(); androidRobot3DObject.getChildAt(i).textures().addById("squared_robot_head"); } if(name.indexOf("foot")!=-1) { androidRobot3DObject.getChildAt(i).textures().clear(); androidRobot3DObject.getChildAt(i).textures().addById("squared_robot_foot"); } if(name.indexOf("arm")!=-1) { androidRobot3DObject.getChildAt(i).textures().clear(); androidRobot3DObject.getChildAt(i).textures().addById("squared_robot_arm"); } if(name.indexOf("antenna")!=-1) { androidRobot3DObject.getChildAt(i).textures().clear(); androidRobot3DObject.getChildAt(i).textures().addById("squared_robot_antenna"); } } }
If you want to load other textures (like the business texture for our robot) for a specific part of the model you can also directly use following line:
androidRobot3DObject.getChildByName("nameOfChild")
How do we know the child’s name?
Either you can check the element directly in Blender
Or if you export to OBJ you will notice one special line in the *.mtl file after each material definition. The name after the letter “o” is the name of the part (child) you can target. Note that only the first part (the characters until the _ (underscore symbol) is reached ) of the name is taken in account when you use this function.
o head_Cube.001 // -> to access this you must then call: androidRobot3DObject.getChildByName("head") and not "head_Cube.001"
Note: If you try to follow this tutorial and want to load the squared_robot as OBJ file, you will notice that the texture switch will not work as expected L Yes unfortunately the texture are curiously stretched.
IParser myParser = Parser.createParser(Parser.Type.OBJ, getResources(), "com.min3dtest:raw/square_robot_obj",false)
Download files
Content of the zip file:
- squared_robot.blend for Blender 2.6
- 7 textures for the robot
- AndroidRobot3DView.java
- 3DS model of the robot
Final note
The loading of an object and its textures is subject to a lot of curious behaviors in min3D. The best tip I can give you is to try your 3D model and directly test it with min3D to see if everything is correct. Sometimes (even for small objects) you have to flip the normals in order for them to be correctly shown up.
Some problems are known and listed in my previous blog entry (http://www.mat-d.com/site/tutorial-load-a-3d-obj-model-with-min3d-for-android/) and http://www.mat-d.com/site/using-min3d-for-android-frequently-asked-questions-common-texture-issues-blender-import/
I’ve also faced a weird problem with exported OBJ and MTL files: if the number of linked textures is not divisible by 2 the object will never load with textures. The solution was to reduce the number of and to pack textures “islands” together by using as less texture files as possible (and their number should be divisible by 2).
So don’t panic if you don’t directly get 1:1 what you have modeled in Blender and what will be shown by min3D.