I have a java 3d application and this application I load an OBJ file into my scene. How can I assign a texture (a jpg file) to this model?
To be more precise, when I want to assign texture to a primitive java object (e.g. sphere) I use the following:
Sphere sphere = new Sphere(Radius, Primflags, Appearance);
However, when loading and adding an obj file I do:
Scene scene = getSceneFromFile("OBJ file");
myBranchGroup = scene.getSceneGroup();
And in second case, I can find no way of assigning the texture. How should I do that?
You would have to use a program that you made the obj file or were you can load the file. Paint it, then export that file. Then add this code to it outside any methods
static TextureLoader loader = new TextureLoader("C:\\Users\\Sawyera\\Desktop\\Paint Layer 1.jpg",
"RGP", new Container());
static Texture texture = loader.getTexture();
Then
texture.setBoundaryModeS(Texture.WRAP);
texture.setBoundaryModeT(Texture.WRAP);
texture.setBoundaryColor(new Color4f(0.0f, 1.0f, 0.0f, 0.0f));
TextureAttributes texAttr = new TextureAttributes();
texAttr.setTextureMode(TextureAttributes.MODULATE);
Appearance ap = new Appearance();
ap.setTexture(texture);
ap.setTextureAttributes(texAttr);
int primflags = Primitive.GENERATE_NORMALS
+ Primitive.GENERATE_TEXTURE_COORDS;
ObjectFile loader = new ObjectFile(ObjectFile.RESIZE);
Then add this before you assign the model to the scene. Assuming the 3D model varrible is called model
model.setAppearance(ap);
IIRC you need to get the Shape3D node you want to apply the texture to (calling setAppearance(...)) from your branch group, e.g. by using getChild(index) etc. Note that you might to iterate recursively through the children, since the branch group you get might actually contain other groups, so you might find the shapes further down the group tree.
Alternatively you should be able to add an AlternateAppearance object to the branch group.
Related
After each fresh install tiles in my MapView only load after zooming out quite a bit. After that it works fine, but I can't figure out what causes this.
The debug logs are as follows:
D/OsmDroid: SqlCache - Tile doesn't exist: Mapnik/1/0/0
D/OsmDroid: Archives - Tile doesn't exist: /1/0/0
D/OsmDroid: SqlCache - Tile doesn't exist: Mapnik/3/1/3
D/OsmDroid: Archives - Tile doesn't exist: /3/1/3
D/OsmDroid: SqlCache - Tile doesn't exist: Mapnik/5/5/12
...
I have initialized my MapView in onViewCreatedlike this:
map = requireView().findViewById(R.id.map);
map.setTileSource(TileSourceFactory.MAPNIK);
map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
map.setMultiTouchControls(true);
And once I obtained a location fix, the following is executed:
IMapController mapController = map.getController();
mapController.setZoom(10.0);
GeoPoint startingPoint = new GeoPoint(location.getLatitude(), location.getLongitude());
mapController.setCenter(startingPoint);
I have followed the osmdroid tutorial, set the user agent, added the necessary permissions to the manifest and so on. Please let me know, if you need further information.
Edit: I'm using version 6.1.11
I had this exact problem.
It seems that if you initially set any zoom level > 19, tiles are not rendered correctly.
As a workaround I set zoom level to 19 instead of 20. Then the user is able to set it as he likes
Updated:
https://tile.openstreetmap.org/1/0/0.png
This would be the url the tilesource is trying to pull the image from. And as you can see the tile is there.
have you tried invalidating the map to force a redraw?
map.invalidate()
Every time you zoom in/out, OsmDroid checks to see if there is a copy of the tile for every tile index within the map view's current boundary. If a copy exists, it will be copied from the tile cache and drawn on the map. If not, the tile will be downloaded from the online map tile database and drawn on the map. The downloaded tile will be saved in the tile cache for quick retrieval the next time those tile indices are in the view's boundary.
It does, however, involve a significant number of tile module provider objects that handle each event. They are saved in the MapTileModuleProviderBase tile module provider array that is set in MapView.setTileProvider() call. If the map tile download module provider is not included, it will not download any tiles from the Internet/network; instead, it will retrieve a copy from any attached tile module providers: cache tile module provider, assets tile module provider, file archive module provider, and so on. If any of those tile providers are missing from the tile module provider array, the tile for that tile index will not be drawn properly and you will see an empty grey "tile" square.
These tile module providers may refer to the OsmDroid default configuration instance, the DefaultConfigurationProvider, for properties that control tile expiration rate, cache size, and so on. These properties affect the tile draw performance.
If you use OsmDroid-specific tile module provider API (MapsForge, GeoPackage, WMS, etc.) to load online/offline map databases which may change the current tile module provider array structure, follow these steps to properly reset to the MAPNIK database:
//load MAPNIK basemap updateable from Internet and cacheable
IFilesystemCache tileWriter = null;
INetworkAvailablityCheck networkAvailabilityCheck = new NetworkAvailabliltyCheck(getContext());
List<MapTileModuleProviderBase> defaultProviders = new ArrayList<>();
SimpleRegisterReceiver simpleRegisterReceiver = new SimpleRegisterReceiver(getContext());
if (Build.VERSION.SDK_INT < 10) {
tileWriter = new TileWriter();
} else {
tileWriter = new SqlTileWriter();
}
defaultProviders.add(new MapTileAssetsProvider(simpleRegisterReceiver, getContext().getAssets()));
final MapTileAssetsProvider assetsProvider = new MapTileAssetsProvider(
simpleRegisterReceiver, getContext().getAssets(), TileSourceFactory.MAPNIK);
defaultProviders.add(assetsProvider);
final MapTileFileStorageProviderBase cacheProvider =
MapTileProviderBasic.getMapTileFileStorageProviderBase(simpleRegisterReceiver, TileSourceFactory.MAPNIK, tileWriter);
defaultProviders.add(cacheProvider);
final MapTileFileArchiveProvider archiveProvider = new MapTileFileArchiveProvider(
simpleRegisterReceiver, TileSourceFactory.MAPNIK);
defaultProviders.add(archiveProvider);
final MapTileApproximater approximationProvider = new MapTileApproximater();
defaultProviders.add(approximationProvider);
approximationProvider.addProvider(assetsProvider);
approximationProvider.addProvider(cacheProvider);
approximationProvider.addProvider(archiveProvider);
final MapTileDownloader downloaderProvider = new MapTileDownloader(TileSourceFactory.MAPNIK, tileWriter, networkAvailabilityCheck);
defaultProviders.add(downloaderProvider);
MapTileModuleProviderBase[] providerArray = new MapTileModuleProviderBase[defaultProviders.size()];
for (int i = 0; i < defaultProviders.size(); i++) {
providerArray[i] = defaultProviders.get(i);
}
Log.i(IMapView.LOGTAG, String.format("reset MAPNIK: current tile module providers(%d) = %s",
providerArray.length,
Arrays.toString(providerArray)));
MapTileProviderArray obj = new MapTileProviderArray(TileSourceFactory.DEFAULT_TILE_SOURCE, simpleRegisterReceiver, providerArray);
mapView.setTileProvider(obj);
mapView.setTileSource(TileSourceFactory.MAPNIK);
Normally, we don't need to explicitly call MapView.invalidate() (if called from a UI thread) or MapView.postInvalidate() (if called from a non-UI thread) since this is handled by MapView for tile resources.
public void setTileProvider(final MapTileProviderBase base) {
this.mTileProvider.detach();
mTileProvider.clearTileCache();
this.mTileProvider = base;
mTileProvider.getTileRequestCompleteHandlers().add(mTileRequestCompleteHandler);
updateTileSizeForDensity(mTileProvider.getTileSource());
this.mMapOverlay = new TilesOverlay(mTileProvider, this.getContext(), horizontalMapRepetitionEnabled, verticalMapRepetitionEnabled);
mOverlayManager.setTilesOverlay(mMapOverlay);
invalidate();
}
If you change any of MapView's properties in a thread again, you must call the appropriate map view invalidation to force the redraw. Calling invalidation too frequently will have a negative impact on the app's performance.
I have 960x960 spritesheet stored as a png inside of my Libgdx android assets. In a class I have specified towards initializing sprites for use in my game, I am trying to make it so there is a 120x120 sprite cut from the spritesheet (so there should be 64 items in the array). How am I able to do this? This is what I have tried in a similar situation:
public static Texture spritesheet;
public static Sprite[] textures = new Sprite[64];
...
//inside method that gets called once to initialize variables
spritesheet = new Texture(
Gdx.app.getType() == Application.ApplicationType.Android ?
"...Spritesheet.png" :
"android/assets/...Spritesheet.png"
);
spritesheet.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
for (int x = 0; x < 64; x ++) {
textures[x] = new Sprite(spritesheet, x, (x%8), 64, 64);
textures[x].flip(false, true);
}
I then render the sprite in other classes with this:
batch.draw(Assets.textures[0 /*this can be any number*/], (float) x, (float) y, 108, 108);
When I do this, it acts really weird. It says there are elements filled in the array, but there's still Array Index Out of Bounds Exceptions or the sprites just render crazily. Overall, it's not working out. What I'm trying to do is make it so I don't have to initialize 64 different sprites separately, and make it so I can easily change the sprite by changing the index inputted when rendering the sprite so I can do some other things later on, like an animation. How can I go about doing this?
You should use a TextureAtlas for this purpose. A atlas is a file generated automatically from separate images by the LibGDX TexturePacker. It stores everything from image bounds within your sheet to NinePatch information. All you need to do is put your separate images in a folder and run the TexturePacker on that folder. This will create a sheet and a .atlas/.pack file for you that can easily loaded.
There exists a TexturePackerGui if you have difficulty with the commandline but I do recommend the command line or even using it within your app.
What I usually do is create these sheets on the fly when I'm developing. I can easily overwrite separate images and they have a immediate effect after I run my app again. I start with creating a new folder images at the root of my project. Then for each pack I need I create another folder. For this example I create the folder tileset in images. In the DesktopLauncher I will setup this folder to produce a atlas from the images.
TexturePacker.Settings settings = new TexturePacker.Settings();
settings.maxWidth = 1024;
settings.maxHeight = 1024;
The settings file specifies everything about your atlas. The maximum size of a single sheet, if it should strip transparency from images, padding, rotation, etc. They are all very straightforward and you can look these all up in the documentation. Using these settings you can create your atlas.
TexturePacker.process(settings,
"../../images/tileset", //where to find the images to be packed.
"../../android/assets/tileset/", //where to store the atlas and sheet
"tileset"); //what filename to use
Now you can open your .atlas file and you see it uses the filenames as a alias. This alias is used to look them up but let's load the atlas first.
TextureAtlas atlas = new TextureAtlas("tileset/tileset.atlas");
By passing just a string to the constructor it looks by default in your local path which in turn is by default in android/assets/. Now we can ask the atlas to hand over our assets in the sheet.
atlas.findRegion("alias"); //hands over the image on the sheet named "alias"
Looking up textures like this is somewhat expensive. You don't want to look up many textures like this each update so you still need to store them somewhere.
If you name your image sequence like image_01.png, image_02.png, image_03.png it stores them all under the same name and within the atlas it sorts them bu it's index. So if you want a separate array of certain textures you can name them with _xx and get them all in one go:
atlas.findRegions("alias");
This is especially handy for Animation. Just copy your image sequence to a folder and specify it to be packed. Name your sequence correctly and give the regions to the animation. Everything will work right off the bat.
Loading a TextureAtlas with the AssetManager is pretty much the same as a normal Texture except you would specify it's from a TextureAtlas.class. You always load the .atlas which in turn handles your image.
I always use AssetDescriptor to load my assets. If I where you I would get rid of the static Assets.textures[] since that will get you into trouble sooner or later.
//None static AssetManager with getter
private AssetManager manager = new AssetManager();
public AssetManager getManager() { return manager; }
//Specify a descriptor for the atlas, this is static so I can acces it anywhere.
//It's just a descriptor of the asset so this is safe.
public static final AssetDescriptor<TextureAtlas> TileSet = new AssetDescriptor<TextureAtlas>("tileset/tileset.atlas", TextureAtlas.class);
//then just load everything
public void load()
{
manager.load(tileSet);
//... load other stuff
}
Now just pass the AssetManager object anywhere you need access to your assets and you can load any asset just like so:
TextureAtlas tileset = assetManager.get(Assets.TileSet);
I think your for loop should look like this
for(int x = 0; x < 64; x ++){
textures[x] = new Sprite(
spritesheet,
(x%8)*64, //where x=3, (3%8)*64 = 3*64 = 192px sourceX
(x/8)*64, //where x=3, (int)(3/8)*64 = 0*64 = 0px sourceY
64, //source width
64 //source height
);
Another test case where x=20;
(20%8)*64 = 4*64 = 256px SourceX
(20/8)*64 = 2*64 = 128px SourceY
I have a problem with the rendering of a json object created with Box2D Editor .
My problem is that even if the object septum (correctly according to the tutorial found online ) , it is not seen on the stage.
private void createBottle() {
// 0. Create a loader for the file saved from the editor.
BodyEditorLoader loader = new BodyEditorLoader(Gdx.files.internal("ball.json"));
// 1. Create a BodyDef, as usual.
BodyDef bd = new BodyDef();
bd.position.set(spaceShip.getX(), spaceShip.getY());
bd.type = BodyDef.BodyType.DynamicBody;
// 2. Create a FixtureDef, as usual.
FixtureDef fd = new FixtureDef();
fd.density = 1;
fd.friction = 0.5f;
fd.restitution = 0.3f;
// 3. Create a Body, as usual.
bottleModel = world.createBody(bd);
// 4. Create the body fixture automatically by using the loader.
loader.attachFixture(bottleModel, "test01", fd, 8);
}
In the create method i have this:
createBottle();
in the Render method:
Vector2 bottlePos = bottleModel.getPosition().sub(bottleModelOrigin);
bottleSprite.setPosition(bottlePos.x, bottlePos.y);
bottleSprite.setOrigin(bottleModelOrigin.x, bottleModelOrigin.y);
bottleSprite.setRotation(bottleModel.getAngle() * MathUtils.radiansToDegrees);
bottleSprite.draw(batch);
I do not understand why it is not added , I can not see on the screen .... I have other items assigned with the z position ... but nothing does not want to appear .
My goal is to enter the mazes made with box2d editor and will move into a commanding object with a virtual joystick , this is the game I'm doing ...
if someone wants to enlighten me on how to import successfully , it is the first time you use an external editor with import json .
EDIT
I tried to follow the tutorial also before posting my question here on .
However the error is on Vector2 .... also the tutorial is clear but not working ... even online I saw other examples that they said that did not work ... there is not a resource running around? I have not found yet
You might try as it is stated here here:
LibGDX draws images with a reference to their bottom left corner. Therefore, if you left the reference point at the bottom left corner of your body, you can directly place the image at the location of the reference point (returned by Box2d engine with the getPosition() method).Else, if you changed the reference point location, you need to take it into account to draw the image.
Therefore, you need to store the reference point somewhere for later use:
bottleModelOrigin = loader.getOrigin("test01", BOTTLE_WIDTH).cpy();
Finally, in your render method, you can draw the image at the reference point location, offset by its local coordinates relatively to the image bottom left corner:
public void render() {
Vector2 bottlePos = bottleModel.getPosition().sub(bottleModelOrigin);
bottleSprite.setPosition(bottlePos.x, bottlePos.y);
bottleSprite.setOrigin(bottleModelOrigin.x, bottleModelOrigin.y);
bottleSprite.setRotation(bottleModel.getAngle() * MathUtils.radiansToDegrees);
}
The problem in your code seems to be that you don't have a variable named bottleModelOrigin atleast it is not visible in your first example. Although in that case a NullPointerException should have been thrown.
My problem is that I am trying to import a 3D model from an STL file to a JavaFX application. I followed the code in this link How to create 3d shape from STL in JavaFX 8? and it's only working with the jewel file mentioned there, but I've tried with other STL files and it's not working!
I can't see why it's not working with the other files. Can anyone explain this?
Any help please, as soon as possible!
As you are already using an STL importer from this site, you will find in the same web a 3D model browser you can use to preview your models before importing them to your JavaFX application.
If they can't be imported with this browser, the problem may be related to a non valid STL format in your files.
If they are imported, then the problem may be in your application. Embed the call in a try-catch and post the exception you may enconter.
StlMeshImporter stlImporter = new StlMeshImporter();
try {
stlImporter.read(this.getClass().getResource("<STLfile>.stl"));
}
catch (ImportException e) {
e.printStackTrace();
return;
}
EDIT
If no exception is thrown while reading the model, the next step would be inserting the returned mesh into a MeshView and show it on our scene:
TriangleMesh mesh = stlImporter.getImport();
stlImporter.close();
MeshView mesh=new MeshView(cylinderHeadMesh);
Group root = new Group(mesh);
Scene scene = new Scene(root, 1024, 800, true);
Camera camera = new PerspectiveCamera();
scene.setCamera(camera);
primaryStage.setScene(scene);
primaryStage.show();
Since the model could be too small or too big for our scene (related to the camera and the point of view we are using), we should print the bounding box of our model, and then scale it up or down accordingly:
System.out.println("mesh: "+mesh.getBoundsInLocal().toString());
mesh.setScaleX(1d);
mesh.setScaleY(1d);
mesh.setScaleZ(1d);
Or we could change the camera parameters:
double max = Math.max(mesh.getBoundsInLocal().getWidth(),
Math.max(mesh.getBoundsInLocal().getHeight(),
mesh.getBoundsInLocal().getDepth()));
camera.setTranslateZ(-3*max);
I have a mesh generated from data in Renderables. Environment is set. Material is a simple new Material().
... /*init renderable*/
/*set mesh parameters*/
renderable.mesh = new Mesh(false,
(int)(meshVertexArray.length/SurfaceBuilder.__ELEMENTSPERVERTEX__), /*!vertices, not cordinates*/
meshIndexArray.length,
new VertexAttribute(Usage.Position,3,"a_position"),
new VertexAttribute(Usage.Normal,3,"a_normal"),
new VertexAttribute(Usage.TextureCoordinates,2,"a_texCoords"),
new VertexAttribute(Usage.ColorPacked,4, "a_color")
);
... /*set vertices*/
The mesh is generated properly, but I can't see the textures, only the gray (shaded) triangles. I did try the rtfm method, but so far I saw no way to bind a texture, so it displays properly in libGDX, only with shaders, and I'm not using them (I'm catching up on them after this feature is implemented). Is there a way in libGDX to bind textures to a mesh without shaders?
Without seeing your texturing code, maybe try specify a texture for your material using the following format:
Material mat = new Material();
//set the diffuse channel on the texture using some texture
mat.set(TextureAttribute.createDiffuse(new Texture("crate.jpg")));