Reputation: 1
I am currently developing an app that requires functionality to move a 3D model. However, I am having trouble implementing the movement feature. At the moment, I can only rotate the 3D model, but I am unable to move it. Can anyone assist me with this issue?
package com.example.roomify_1;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.MotionEvent;
import android.view.PixelCopy;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.ar.core.exceptions.CameraNotAvailableException;
import com.google.ar.sceneform.Camera;
import com.google.ar.sceneform.HitTestResult;
import com.google.ar.sceneform.SceneView;
import com.google.ar.sceneform.assets.RenderableSource;
import com.google.ar.sceneform.math.Quaternion;
import com.google.ar.sceneform.math.Vector3;
import com.google.ar.sceneform.rendering.ModelRenderable;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.ux.FootprintSelectionVisualizer;
import com.google.ar.sceneform.ux.TransformableNode;
import com.google.ar.sceneform.ux.TransformationSystem;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import android.Manifest;
public class RoomDesigning extends AppCompatActivity {
private static final String TAG = "RoomDesigning";
private static final int REQUEST_WRITE_STORAGE = 1;
private SceneView mSceneView;
private TransformationSystem transformationSystem;
private FirebaseFirestore db;
private final List<Furniture> furnitureList = new ArrayList<>();
private RecyclerView recyclerView;
private FurnitureAdapter furnitureAdapter;
private BottomSheetDialog bottomSheetDialog;
private Vector3 roomScale; // To store room dimensions
private Stack<TransformableNode> furnitureStack;
private Map<String, Integer> selectedFurnitureMap = new HashMap<>();
private TransformableNode roomNode;
private TransformableNode selectedNode;
private float previousX; // Declare previousX at the class level
private float previousY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room_designing);
db = FirebaseFirestore.getInstance();
mSceneView = findViewById(R.id.sceneView);
transformationSystem = new TransformationSystem(getResources().getDisplayMetrics(), new FootprintSelectionVisualizer());
furnitureStack = new Stack<>();
findViewById(R.id.undobutton).setOnClickListener(v -> undoLastAction());
ImageView cameraButton = findViewById(R.id.camerabutton); // Ensure you have this button in your layout
cameraButton.setOnClickListener(v -> takeScreenshot());
findViewById(R.id.bagbutton).setOnClickListener(v -> showSelectedFurnitureDetails());
Camera camera = mSceneView.getScene().getCamera();
camera.setLocalPosition(new Vector3(0f, 3f, 0f));
Quaternion rotation = Quaternion.axisAngle(new Vector3(1f, 0f, 0f), -90f);
camera.setLocalRotation(rotation);
// Setup touch listener for object manipulation
mSceneView.setOnTouchListener((view, motionEvent) -> {
try {
HitTestResult hitTestResult = mSceneView.getScene().hitTest(motionEvent);
if (hitTestResult == null || hitTestResult.getNode() == null) {
Log.d(TAG, "No node found at touch location.");
return true; // No node found, just consume the event
}
transformationSystem.onTouch(hitTestResult, motionEvent);
// Check if the node is a TransformableNode and it's not the room node
if (hitTestResult.getNode() instanceof TransformableNode && hitTestResult.getNode() != roomNode) {
selectedNode = (TransformableNode) hitTestResult.getNode();
if (motionEvent.getPointerCount() == 1) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "Node selected: " + selectedNode.getName());
previousX = motionEvent.getX();
previousY = motionEvent.getY();
break;
case MotionEvent.ACTION_MOVE:
if (selectedNode != null) {
// Calculate the delta movement
float deltaX = motionEvent.getX() - previousX;
float deltaY = motionEvent.getY() - previousY;
// Update previous touch coordinates
previousX = motionEvent.getX();
previousY = motionEvent.getY();
// Get the current position of the selected node
Vector3 currentPosition = selectedNode.getLocalPosition();
// Convert the touch movement to scene coordinates
float scaleFactorX = roomScale.x / mSceneView.getWidth();
float scaleFactorZ = roomScale.z / mSceneView.getHeight();
// Update the position of the selected node
Vector3 newPosition = new Vector3(
currentPosition.x + deltaX * scaleFactorX,
currentPosition.y, // Keep the Y position constant
currentPosition.z + deltaY * scaleFactorZ
);
selectedNode.setLocalPosition(newPosition);
Log.d(TAG, "Node moved to: " + newPosition);
}
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "Node released: " + selectedNode.getName());
selectedNode = null; // Reset the selected node
view.performClick(); // Call performClick() here
break;
}
}
}
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
view.performClick(); // Ensure click events work as expected
}
} catch (Exception e) {
Log.e(TAG, "Error during touch handling: ", e);
Toast.makeText(view.getContext(), "Error interacting with model", Toast.LENGTH_SHORT).show();
}
return true;
});
// Prompt user for room dimensions
getRoomDimensionsFromUser();
findViewById(R.id.seatingbutton).setOnClickListener(v -> loadFurniture("SeatingFurnitures"));
findViewById(R.id.bedsbutton).setOnClickListener(v -> loadFurniture("Beds"));
findViewById(R.id.tableschairsbutton).setOnClickListener(v -> loadFurniture("TablesChairs"));
findViewById(R.id.storagebutton).setOnClickListener(v -> loadFurniture("Storage"));
findViewById(R.id.xbutton).setOnClickListener(v -> {
Intent intent = new Intent(RoomDesigning.this, RoomTemplate.class);
startActivity(intent);
});
}
// Function to prompt user for room dimensions
private void getRoomDimensionsFromUser() {
BottomSheetDialog dialog = new BottomSheetDialog(this);
View dialogView = getLayoutInflater().inflate(R.layout.dialog_room_dimensions, null);
dialog.setContentView(dialogView);
EditText depthInput = dialogView.findViewById(R.id.depthInput); // Changed from depth to length
EditText widthInput = dialogView.findViewById(R.id.widthInput);
EditText heightInput = dialogView.findViewById(R.id.heightInput);
dialogView.findViewById(R.id.submitButton).setOnClickListener(v -> {
float depth = Float.parseFloat(depthInput.getText().toString()); // Changed from depth to length
float width = Float.parseFloat(widthInput.getText().toString());
float height = Float.parseFloat(heightInput.getText().toString());
// Set the room scale based on user input
roomScale = calculateRoomScale(width, height, depth); // Updated function for scaling
createScene(); // Load the room after dimensions are set
dialog.dismiss();
});
dialog.show();
}
// New method to calculate the room scale based on the largest dimension
private Vector3 calculateRoomScale(float width, float height, float length) { // Changed depth to length
// Find the largest dimension to keep aspect ratio
float maxDimension = Math.max(Math.max(width, height), length); // Updated for length
// Scale the room to fit within a normalized range (e.g., target max size = 2 units)
float scaleFactor = 1.5f / maxDimension;
// Apply the same scale to all dimensions to maintain aspect ratio
return new Vector3(width * scaleFactor, height * scaleFactor, length * scaleFactor); // Updated for length
}
private void createScene() {
ModelRenderable.builder()
.setSource(this, RenderableSource.builder()
.setSource(this, Uri.parse("file:///android_asset/rectangular_room_template.glb"), RenderableSource.SourceType.GLB)
.setRecenterMode(RenderableSource.RecenterMode.ROOT)
.build())
.setRegistryId("rectangular_room_template_1.glb")
.build()
.thenAccept(this::onRenderableLoaded)
.exceptionally(throwable -> {
Log.e(TAG, "Failed to load model", throwable);
Toast.makeText(this, "Error: Model could not be loaded", Toast.LENGTH_SHORT).show();
return null;
});
}
private void onRenderableLoaded(Renderable renderable) {
if (renderable != null) {
roomNode = new TransformableNode(transformationSystem); // Store the room node reference
roomNode.setRenderable(renderable);
roomNode.setLocalPosition(new Vector3(0f, 0f, 0f));
roomNode.setLocalScale(roomScale);
roomNode.getScaleController().setEnabled(false);
roomNode.getRotationController().setEnabled(true);
mSceneView.getScene().addChild(roomNode);
transformationSystem.selectNode(roomNode);
Log.d(TAG, "Room node added to scene");
} else {
Log.e(TAG, "Renderable is null");
}
}
private void loadFurniture(String category) {
CollectionReference furnitureRef = db.collection("furnitures");
furnitureRef.whereEqualTo("category", category).get().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
furnitureList.clear();
for (QueryDocumentSnapshot document : task.getResult()) {
Furniture furniture = document.toObject(Furniture.class);
furnitureList.add(furniture);
}
showBottomDrawer(category);
} else {
Log.e(TAG, "Error fetching furniture: ", task.getException());
Toast.makeText(RoomDesigning.this, "Error loading furniture", Toast.LENGTH_SHORT).show();
}
});
}
private void showBottomDrawer(String category) {
bottomSheetDialog = new BottomSheetDialog(this);
View bottomSheetView = getLayoutInflater().inflate(R.layout.fragment_bottom_sheet_dialog, null);
bottomSheetDialog.setContentView(bottomSheetView);
TextView categoryTitle = bottomSheetView.findViewById(R.id.categoryTitle);
categoryTitle.setText(category.substring(0, 1).toUpperCase() + category.substring(1));
recyclerView = bottomSheetView.findViewById(R.id.furnitureRecyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
furnitureAdapter = new FurnitureAdapter(this, furnitureList);
recyclerView.setAdapter(furnitureAdapter);
furnitureAdapter.setOnItemClickListener(furniture -> {
addFurnitureToScene(furniture);
bottomSheetDialog.dismiss();
});
bottomSheetDialog.show();
}
private void showSelectedFurnitureDetails() {
Log.d(TAG, "Selected furniture map: " + selectedFurnitureMap.toString()); // Log the map content
BottomSheetDialog dialog = new BottomSheetDialog(this);
View dialogView = getLayoutInflater().inflate(R.layout.fragment_dialog_selected_furniture, null);
dialog.setContentView(dialogView);
RecyclerView selectedFurnitureRecyclerView = dialogView.findViewById(R.id.selectedFurnitureRecyclerView);
selectedFurnitureRecyclerView.setLayoutManager(new LinearLayoutManager(this));
List<SelectedFurnitureList> selectedItems = new ArrayList<>();
final float[] totalPrice = {0}; // Use an array to modify the value inside the lambda
// Check if selectedFurnitureMap is empty
if (selectedFurnitureMap.isEmpty()) {
Toast.makeText(RoomDesigning.this, "No furniture selected", Toast.LENGTH_SHORT).show();
return;
}
// Iterate through selectedFurnitureMap entries
for (Map.Entry<String, Integer> entry : selectedFurnitureMap.entrySet()) {
String furnitureName = entry.getKey();
int quantity = entry.getValue();
// Fetch furniture details from Firestore
db.collection("furnitures").whereEqualTo("name", furnitureName).get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
// Process each document found for the furniture item
for (QueryDocumentSnapshot document : task.getResult()) {
try {
Furniture furniture = document.toObject(Furniture.class);
String priceStr = furniture.getPrice();
if (priceStr != null) {
float price = Float.parseFloat(priceStr); // Convert String to float
totalPrice[0] += price * quantity;
// Add the item to the list
selectedItems.add(new SelectedFurnitureList(
furniture.getName() != null ? furniture.getName() : "Unknown Name",
furniture.getDimensions() != null ? furniture.getDimensions() : "Unknown Dimensions",
price, // Converted to float
quantity,
furniture.getImageURL() != null ? furniture.getImageURL() : ""
));
} else {
Log.e(TAG, "Price is null for furniture: " + furnitureName);
// Handle the case where the price is null
}
} catch (Exception e) {
Log.e(TAG, "Error processing furniture data: ", e);
}
}
// Check if all items have been processed
displaySelectedFurniture(dialog, dialogView, selectedFurnitureRecyclerView, selectedItems, totalPrice[0]);
} else {
Log.e(TAG, "Error fetching furniture: ", task.getException());
Toast.makeText(RoomDesigning.this, "Error loading selected furniture", Toast.LENGTH_SHORT).show();
}
})
.addOnFailureListener(e -> {
Log.e(TAG, "Error fetching furniture: ", e);
Toast.makeText(RoomDesigning.this, "Error loading selected furniture", Toast.LENGTH_SHORT).show();
});
}
}
// Method to display the selected furniture and total price
private void displaySelectedFurniture(BottomSheetDialog dialog, View dialogView, RecyclerView recyclerView, List<SelectedFurnitureList> items, float totalPrice) {
// Ensure there are items to display
if (items.isEmpty()) {
Toast.makeText(RoomDesigning.this, "No items to display", Toast.LENGTH_SHORT).show();
return;
}
// Set the adapter and show the dialog after all items have been added to the list
SelectedFurnitureAdapter adapter = new SelectedFurnitureAdapter(items);
recyclerView.setAdapter(adapter);
// Update the total price TextView
TextView totalPriceTextView = dialogView.findViewById(R.id.totalPriceTextView);
totalPriceTextView.setText("Total Price: RM " + totalPrice);
dialog.show();
}
private void addFurnitureToScene(Furniture furniture) {
Uri modelUri = Uri.parse(furniture.getModelURL());
Log.d(TAG, "Attempting to load model from: " + modelUri.toString());
runOnUiThread(() -> {
ModelRenderable.builder()
.setSource(this, RenderableSource.builder()
.setSource(this, modelUri, RenderableSource.SourceType.GLB)
.setRecenterMode(RenderableSource.RecenterMode.ROOT)
.build())
.setRegistryId(furniture.getName())
.build()
.thenAccept(renderable -> {
onFurnitureLoaded(renderable, furniture);
// Update the selected furniture map after loading the furniture
selectedFurnitureMap.put(furniture.getName(), selectedFurnitureMap.getOrDefault(furniture.getName(), 0) + 1);
})
.exceptionally(throwable -> {
Log.e(TAG, "Failed to load furniture model", throwable);
Toast.makeText(this, "Error: Model could not be loaded", Toast.LENGTH_SHORT).show();
return null;
});
});
}
private void onFurnitureLoaded(Renderable renderable, Furniture furniture) {
TransformableNode furnitureNode = new TransformableNode(transformationSystem);
furnitureNode.setRenderable(renderable);
// Position the furniture at the origin (adjust if needed)
furnitureNode.setLocalPosition(new Vector3(0f, 0f, 0f));
// Use the dimensions directly from the database (in meters)
float furnitureWidth = furniture.getScaledWidth() * roomScale.x; // Scale the width
float furnitureHeight = furniture.getScaledHeight() * roomScale.y; // Scale the height
float furnitureDepth = furniture.getScaledDepth() * roomScale.z; // Scale the depth
// Set the scale for the furniture model
furnitureNode.setLocalScale(new Vector3(furnitureWidth, furnitureHeight, furnitureDepth));
// Enable translation (movement) for the furniture model
furnitureNode.getTranslationController().setEnabled(true);
mSceneView.getScene().addChild(furnitureNode);
transformationSystem.selectNode(furnitureNode);
furnitureStack.push(furnitureNode);
}
private void undoLastAction() {
if (!furnitureStack.isEmpty()) {
TransformableNode lastFurniture = furnitureStack.pop();
mSceneView.getScene().removeChild(lastFurniture); // Remove from the scene
// Instead of setVisible, you can handle the visibility using logs or messages
Toast.makeText(this, "Last action undone", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "No actions to undo", Toast.LENGTH_SHORT).show();
}
}
private void takeScreenshot() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
} else {
// Permissions are granted, take the screenshot
final Bitmap bitmap = Bitmap.createBitmap(mSceneView.getWidth(), mSceneView.getHeight(), Bitmap.Config.ARGB_8888);
PixelCopy.request(mSceneView, bitmap, copyResult -> {
if (copyResult == PixelCopy.SUCCESS) {
saveImageToGallery(bitmap);
} else {
Toast.makeText(this, "Failed to capture screenshot", Toast.LENGTH_SHORT).show();
}
}, new Handler(Looper.getMainLooper()));
}
}
private void saveImageToGallery(Bitmap bitmap) {
String filename = "AR_Screenshot_" + System.currentTimeMillis() + ".png";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File imageFile = new File(storageDir, filename);
try (FileOutputStream out = new FileOutputStream(imageFile)) {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
// Trigger media scanner to add the image to the gallery
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)));
Toast.makeText(this, "Screenshot saved to gallery", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "Error saving screenshot", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onPause() {
super.onPause();
mSceneView.pause();
}
@Override
protected void onResume() {
super.onResume();
try {
mSceneView.resume();
} catch (CameraNotAvailableException e) {
Log.e(TAG, "Failed to resume SceneView", e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mSceneView.destroy();
}
}
I hope anyone can help me to solve this issue.
Upvotes: 0
Views: 15