Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAD controls for trackpads #5

Closed
giiyms opened this issue Feb 8, 2023 · 5 comments
Closed

CAD controls for trackpads #5

giiyms opened this issue Feb 8, 2023 · 5 comments
Assignees
Labels
enhancement New feature or request question Further information is requested

Comments

@giiyms
Copy link

giiyms commented Feb 8, 2023

Hello,

I am trying to recreate similar controls to 3D CAD packages on Windows using a trackpad.

I would like to:
Rotation with two finger pan on trackpad
Hold shift then click or two finger pan on trackpad to translate the model.
Hold ctrl then click or two finger pan on trackpad to zoom in based on y axis movement.

I noticed that trackpad 2 finger scroll also doesn't register as a scroll event, any ideas?

Is there a way to update the center of rotation? i.e. click near a node, then its snaps the rotation origin to that location.

Thanks

import 'package:flutter/material.dart';
import 'package:simple_3d/simple_3d.dart';
import 'package:util_simple_3d/util_simple_3d.dart';
import 'package:simple_3d_renderer/simple_3d_renderer.dart';

void main() async {
  runApp(const HomePage());
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sp3dRenderer',
      home: Scaffold(
        appBar: AppBar(
          backgroundColor: const Color.fromARGB(255, 0, 255, 0),
        ),
        backgroundColor: const Color.fromARGB(255, 33, 33, 33),
        body: Renderer(),
      ),
    );
  }
}

class Renderer extends StatefulWidget {
  const Renderer({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _RendererState();
}

class _RendererState extends State<Renderer> {
  late List<Sp3dObj> objs = [];
  late Sp3dWorld world;
  bool isLoaded = false;

  // Add variable to _MyAppState.
  ValueNotifier<int> vn = ValueNotifier<int>(0);

  @override
  void initState() {
    super.initState();
    // Create Sp3dObj.
    Sp3dObj obj = UtilSp3dGeometry.cube(200, 200, 200, 4, 4, 4);
    obj.materials.add(FSp3dMaterial.green.deepCopy());
    obj.fragments[0].faces[0].materialIndex = 1;
    obj.materials[0] = FSp3dMaterial.grey.deepCopy()
      ..strokeColor = const Color.fromARGB(255, 0, 0, 255);
    obj.rotate(Sp3dV3D(1, 1, 0).nor(), 30 * 3.14 / 180);
    objs.add(obj);
    loadImage();
  }

  void loadImage() async {
    world = Sp3dWorld(objs);
    world.initImages().then((List<Sp3dObj> errorObjs) {
      setState(() {
        isLoaded = true;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    final worldSize = MediaQuery.of(context).size;

    if (!isLoaded) {
      return Container(
        child: Text("Error"),
      );
    } else {
      return Column(
        children: [
          // Rewrite Sp3dRenderer.
          Sp3dRenderer(
            Size(worldSize.width, worldSize.height - 100),
            Sp3dV2D(worldSize.width / 2, worldSize.height / 2),
            // Sp3dV2D(0, 0),
            world,
            // If you want to reduce distortion, shoot from a distance at high magnification.
            Sp3dCamera(Sp3dV3D(0, 0, 30000), 60000),
            Sp3dLight(Sp3dV3D(0, 0, -1), syncCam: true),
            allowUserWorldRotation: true,
            checkTouchObj: true,
            isMouseScrollSameVector: true,
            allowUserWorldZoom: true,
            vn: vn,
            // onPanDown: (Sp3dGestureDetails d, Sp3dFaceObj? info) {
            //   print("onPanDown");
            //   if (info != null) {
            //     info.obj.move(Sp3dV3D(50, 50, 0));
            //     vn.value++;
            //   }
            // },
            // onPanCancel: () {
            //   print("onPanCancel");
            // },
            // onPanStart: (Sp3dGestureDetails d) {
            //   print("onPanStart");
            //   print(d.toOffset());
            // },
            // onPanUpdate: (Sp3dGestureDetails d) {
            //   print("onPanUpdate");
            //   print(d.toOffset());

            //   vn.value++;
            // },
            // onPanEnd: (Sp3dGestureDetails d) {
            //   print("onPanEnd");
            // },
            // onPinchStart: (Sp3dGestureDetails d) {
            //   print("onPinchStart");
            //   print(d.diffV);
            // },
            // onPinchUpdate: (Sp3dGestureDetails d) {
            //   print("onPinchUpdate");
            //   print(d.diffV);
            // },
            // onPinchEnd: (Sp3dGestureDetails d) {
            //   print("onPinchEnd");
            //   print(d.diffV);
            // },
            // onMouseScroll: (Sp3dGestureDetails d) {
            //   print("onMouseScroll");
            //   print(d.diffV);
            // },
            // onSecondPanDown: (Sp3dGestureDetails d, Sp3dFaceObj? info) {
            //   print("onSecondPanDown");
            //   print(d.diffV);
            // },
          )
        ],
      );
    }
  }
}
@MasahideMori-SimpleAppli
Copy link
Owner

Hi @giiyms,

Thanks for opening this issue.
I didn't know that Flutter 3.3.0 has trackpad support.
https://docs.flutter.dev/release/breaking-changes/trackpad-gestures

I think it would be better if there was trackpad support, so I will consider adding it as a feature.
If it can be added, I will probably add this feature in the next update.

@MasahideMori-SimpleAppli
Copy link
Owner

About changing the center of rotation.
It's probably easiest to move the whole object in the world.
What you want to do is something like the following?
After running this code, two cubes will appear.
Try tapping the cube.

import 'package:flutter/material.dart';
import 'package:simple_3d/simple_3d.dart';
import 'package:util_simple_3d/util_simple_3d.dart';
import 'package:simple_3d_renderer/simple_3d_renderer.dart';

void main() async {
  runApp(const HomePage());
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sp3dRenderer',
      home: Scaffold(
        appBar: AppBar(
          backgroundColor: const Color.fromARGB(255, 0, 255, 0),
        ),
        backgroundColor: const Color.fromARGB(255, 33, 33, 33),
        body: const Renderer(),
      ),
    );
  }
}

class Renderer extends StatefulWidget {
  const Renderer({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _RendererState();
}

class _RendererState extends State<Renderer> {
  late List<Sp3dObj> objs = [];
  late Sp3dWorld world;
  bool isLoaded = false;

  // Add variable to _MyAppState.
  ValueNotifier<int> vn = ValueNotifier<int>(0);

  // Have a camera object in the parent widget.
  final Sp3dCamera _camera = Sp3dCamera(Sp3dV3D(0, 0, 30000), 60000);

  // Keep it in the parent widget so you can access and resume the camera rotation state.
  final Sp3dCameraRotationController? _rCtrl = Sp3dCameraRotationController();

  @override
  void initState() {
    super.initState();
    // Create Sp3dObj.
    Sp3dObj cubeA = UtilSp3dGeometry.cube(200, 200, 200, 4, 4, 4,
        material: FSp3dMaterial.blue.deepCopy());
    cubeA.name = "cubeA";
    cubeA.rotate(Sp3dV3D(1, 1, 0).nor(), 30 * 3.14 / 180);
    objs.add(cubeA);
    Sp3dObj cubeB = UtilSp3dGeometry.cube(200, 200, 200, 4, 4, 4,
        material: FSp3dMaterial.green.deepCopy());
    cubeB.name = "cubeB";
    cubeB.move(Sp3dV3D(300, 0, 0));
    cubeB.rotate(Sp3dV3D(1, 1, 0).nor(), 30 * 3.14 / 180);
    objs.add(cubeB);
    loadImage();
  }

  void loadImage() async {
    world = Sp3dWorld(objs);
    world.initImages().then((List<Sp3dObj> errorObjs) {
      setState(() {
        isLoaded = true;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    final worldSize = MediaQuery.of(context).size;
    if (!isLoaded) {
      return const Text("Error");
    } else {
      return Column(
        children: [
          // Rewrite Sp3dRenderer.
          Sp3dRenderer(
            Size(worldSize.width, worldSize.height - 100),
            Sp3dV2D(worldSize.width / 2, worldSize.height / 2),
            world,
            _camera,
            Sp3dLight(Sp3dV3D(0, 0, -1), syncCam: true),
            allowUserWorldRotation: true,
            checkTouchObj: true,
            isMouseScrollSameVector: true,
            allowUserWorldZoom: true,
            vn: vn,
            onPanDown: (Sp3dGestureDetails d, Sp3dFaceObj? info) {
              print("onPanDown");
              if (info != null) {
                print(info.obj.name! + " tapped!");
                Sp3dV3D targetCenter = info.obj.getCenter();
                // move world objs
                for (Sp3dObj i in world.objs) {
                  i.move(targetCenter * -1);
                }
                vn.value++;
              }
            },
            rotationController: _rCtrl,
          )
        ],
      );
    }
  }
}

@MasahideMori-SimpleAppli MasahideMori-SimpleAppli added enhancement New feature or request question Further information is requested labels Feb 8, 2023
@MasahideMori-SimpleAppli
Copy link
Owner

Hi @giiyms,
I upgraded this package to version 9.1.0.
Newer versions support trackpads on Windows.

@MasahideMori-SimpleAppli
Copy link
Owner

Below is a sample that moves the model based on the camera and zooms based on the y-axis operation in the new version.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:simple_3d/simple_3d.dart';
import 'package:simple_3d_renderer/simple_3d_renderer.dart';
import 'package:util_simple_3d/util_simple_3d.dart';

void main() async {
  runApp(const HomePage());
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sp3dRenderer',
      home: Scaffold(
        appBar: AppBar(
          backgroundColor: const Color.fromARGB(255, 0, 255, 0),
        ),
        backgroundColor: const Color.fromARGB(255, 33, 33, 33),
        body: const Renderer(),
      ),
    );
  }
}

class Renderer extends StatefulWidget {
  const Renderer({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _RendererState();
}

class _RendererState extends State<Renderer> {
  late List<Sp3dObj> objs = [];
  late Sp3dWorld world;
  bool isLoaded = false;

  // Add variable to _MyAppState.
  ValueNotifier<int> vn = ValueNotifier<int>(0);

  // Have a camera object in the parent widget.
  final Sp3dCamera _camera = Sp3dCamera(Sp3dV3D(0, 0, 30000), 60000);

  // Keep it in the parent widget so you can access and resume the camera rotation state.
  final Sp3dCameraRotationController _rCtrl = Sp3dCameraRotationController();

  Sp3dObj? _tapObj;
  bool _isPressCtrlNow = false;
  bool _isPressShiftNow = false;

  @override
  void initState() {
    super.initState();
    // Create Sp3dObj.
    Sp3dObj cubeA = UtilSp3dGeometry.cube(200, 200, 200, 4, 4, 4,
        material: FSp3dMaterial.blue.deepCopy());
    cubeA.name = "cubeA";
    cubeA.rotate(Sp3dV3D(1, 1, 0).nor(), 30 * 3.14 / 180);
    objs.add(cubeA);
    Sp3dObj cubeB = UtilSp3dGeometry.cube(200, 200, 200, 4, 4, 4,
        material: FSp3dMaterial.green.deepCopy());
    cubeB.name = "cubeB";
    cubeB.move(Sp3dV3D(300, 0, 0));
    cubeB.rotate(Sp3dV3D(1, 1, 0).nor(), 30 * 3.14 / 180);
    objs.add(cubeB);
    loadImage();
  }

  void loadImage() async {
    world = Sp3dWorld(objs);
    world.initImages().then((List<Sp3dObj> errorObjs) {
      setState(() {
        isLoaded = true;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    final worldSize = MediaQuery.of(context).size;
    if (!isLoaded) {
      return const Text("Error");
    } else {
      return Focus(
          autofocus: true,
          onKeyEvent: (node, event) {
            if (event is KeyDownEvent) {
              if (event.physicalKey == PhysicalKeyboardKey.controlLeft ||
                  event.physicalKey == PhysicalKeyboardKey.controlRight) {
                print("press Ctrl");
                _isPressCtrlNow = true;
                return KeyEventResult.handled;
              }
              if (event.physicalKey == PhysicalKeyboardKey.shiftLeft ||
                  event.physicalKey == PhysicalKeyboardKey.shiftRight) {
                print("press Shift");
                _isPressShiftNow = true;
                return KeyEventResult.handled;
              }
            }
            if (event is KeyUpEvent) {
              if (event.physicalKey == PhysicalKeyboardKey.controlLeft ||
                  event.physicalKey == PhysicalKeyboardKey.controlRight) {
                print("up Ctrl");
                _isPressCtrlNow = false;
                return KeyEventResult.handled;
              }
              if (event.physicalKey == PhysicalKeyboardKey.shiftLeft ||
                  event.physicalKey == PhysicalKeyboardKey.shiftRight) {
                print("up Shift");
                _isPressShiftNow = false;
                return KeyEventResult.handled;
              }
            }
            return KeyEventResult.ignored;
          },
          child: Column(
            children: [
              // Rewrite Sp3dRenderer.
              Sp3dRenderer(
                Size(worldSize.width, worldSize.height - 100),
                Sp3dV2D(worldSize.width / 2, worldSize.height / 2),
                world,
                _camera,
                Sp3dLight(Sp3dV3D(0, 0, -1), syncCam: true),
                allowUserWorldRotation: true,
                checkTouchObj: true,
                isMouseScrollSameVector: true,
                allowUserWorldZoom: true,
                vn: vn,
                onPanDown: (Sp3dGestureDetails d, Sp3dFaceObj? info) {
                  print("onPanDown");
                  if (info != null) {
                    print("${info.obj.name!} tapped!");
                    _tapObj = info.obj;
                    Sp3dV3D targetCenter = info.obj.getCenter();
                    // move world objs
                    for (Sp3dObj i in world.objs) {
                      i.move(targetCenter * -1);
                    }
                    vn.value++;
                  } else {
                    print("selected obj clear");
                    _tapObj = null;
                  }
                },
                onPanUpdate: (Sp3dGestureDetails d) {
                  print("onPanUpdate");
                  if (_tapObj != null && _isPressShiftNow) {
                    // Move based on the camera.
                    _tapObj!.rotate(_camera.rotateAxis, _camera.radian);
                    _tapObj!.move(Sp3dV3D(d.diffV.x, -1 * d.diffV.y, 0));
                    _tapObj!.rotate(_camera.rotateAxis, -1 * _camera.radian);
                    vn.value++;
                  }
                  if (_tapObj != null && _isPressCtrlNow) {
                    // Zoom by y axis value.
                    _camera.focusLength += d.diffV.y*100;
                    vn.value++;
                  }
                },
                rotationController: _rCtrl,
              )
            ],
          ));
    }
  }
}

@MasahideMori-SimpleAppli
Copy link
Owner

I'll close this issue for now.
Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Development

No branches or pull requests

2 participants