Nur Pretty
Nur Pretty

Reputation: 1

How to move 3d model in android studio using scenform?

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

Answers (0)

Related Questions