engine icon indicating copy to clipboard operation
engine copied to clipboard

Camera controls other than orbit?

Open quillaja opened this issue 6 years ago • 13 comments

Are there any (camera) controls available besides the orbit control? For example, a FPS-style control?

A quick look at the camera.OrbitControl doesn't show that it implements any sort of control interface, and the application.Application struct has an explicit member for an OrbitControl. I guess this would make adding alternative camera controls require more changes to the codebase than simply adding a struct+methods for the new control.

Do you think replacing the explicit OrbitControl in various places with an interface (eg IControl) would break the API at all? Is abstracting the camera control to an interface and adding another FPS-style control something you'd be interested in? If so, I could potentially work on it.

quillaja avatar May 08 '19 05:05 quillaja

Hey Ben! We only have the orbit control and definitely would like more camera controls. A control interface sounds like a great idea. I'm actually in the middle of a big refactoring of the engine so we should probably wait until that's finished. When the work in the wasm branch is done and merged we can get to work on this and I'd love help. Thanks a lot!

danaugrs avatar May 24 '19 16:05 danaugrs

Sure thing. I haven't had much time lately for any programming, so waiting for the refactoring to be finished works well for me.

quillaja avatar Jun 13 '19 03:06 quillaja

@quillaja Refactoring is finished and merged!

danaugrs avatar Sep 08 '19 14:09 danaugrs

cool! I'll take a look. Can't say I'll get right to it, but I did just (essentially) finish a project I was working on, so go timing. =)

quillaja avatar Sep 08 '19 16:09 quillaja

@quillaja I completely overhauled the camera package and orbit control in https://github.com/g3n/engine/commit/116597ce47660cc601433376c325923f0001b9b5 - should be a lot easier to add other camera controls now 😄

danaugrs avatar Sep 16 '19 00:09 danaugrs

I made a freelook camera that I will send a PR for once I clean it up a bit and make it more configurable

gamerscomplete avatar Nov 22 '19 08:11 gamerscomplete

Thanks. I haven't had time to start on this. Sorry. I still hope to, but it's low priority for me at the moment.

quillaja avatar Nov 22 '19 19:11 quillaja

@danaugrs I actually started working on this...only 2 years late! I browsed the commit history and didn't see that anyone else actually submitted a PR for a fly camera control, so I assume it hasn't already been done.

So far, I have one question. How do I set the cursor mode using G3N's api? I want to do this to toggle "mouse capture" and map mouse up/down to pitch/yaw. In plain glfw I call glfwWindow.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) (or glfw.CursorNormal). G3N doesn't expose this method through the IWindow interface, at least as far as I can tell. I can cast the interface to *glfw.Window and access the method that way, but then the point of using an interface is nullified and then the camera package will depend on glfw.

quillaja avatar Sep 09 '21 03:09 quillaja

Hey @quillaja ! The idea is that IWindow would be used for the portions of the API that are fully cross-platform. If the user wants to do something platform-specific then they need to explicitly cast their instance to that platforms's "window" type - making the platform-dependence explicit. Let me know if you have another idea :)

danaugrs avatar Nov 04 '21 20:11 danaugrs

@danaugrs Thanks for getting back to me. I was considering adding a method to IWindow such as SetInputMode() which could expose that feature of the underlying window. The window package already has the appropriate types and constants defined (at least for glfw, in glfw.go around line 175). Of course, I'd implement and expose the new interface method on the GlfwWindow type as well. It doesn't appear that any other window types are specifically supported in G3N itself, so it should be a fairly minor change to the window package.

That said, I can see the downside of adding additional API, and specifically adding a method to IWindow would break any implementations that others have created in their own programs (though I wonder how many people have actually created their own IWindow implementation).

The alternative that immediately comes to mind is just to make the user handle toggling mouse capture (by casting IWindow to window.GlfwWindow and accessing the SetInputMode() method of the embedded glfw.Window), and then signal to my FlyControl that the mouse is captured or not. However, since the FlyControl, like the OrbitControl, already listens for and handles keyboard and mouse events, it does seem natural that the control would also handle toggling mouse capture as well.

What do you, or other maintainers, think?

As an aside, what I've written so far is modeled on the OrbitControl, but produces a "god mode" like camera that can translate on 3 axes (eg, forward/back, up/down, right/left), rotate on 3 (yaw, pitch, roll), and change the camera FoV (zoom). It takes a user provided mapping of key/mouse to movements, and will provided at least 1 premade mapping. It also has configurable speeds for all movements, and configurable constraints on rotations and zoom. The idea is that it'll be functional with sane defaults and minimal configuration, but allow the user to customize it.

quillaja avatar Nov 05 '21 02:11 quillaja

@quillaja WebGlCanvas is the IWindow implementation for running g3n in the browser. I'm thinking adding a SetCursorMode(mode CursorMode) to IWindow might work fine cross-platform (mobile can just ignore it). Just need to implement that method in GlfwWindow and WebGlCanvas. However I think FlyControl should also work without capturing the mouse. Having the mouse cursor available to interact with the scene can be useful, and it would behave like Unreal's default camera control (i.e. requiring the user to click-hold-drag IIRC). Maybe you can do this in two PRs - first implementation without capturing the mouse and without changing the IWindow API, and then, once that's working, add the option for mouse capture by implementing and using SetCursorMode. What do you think?

danaugrs avatar Nov 05 '21 13:11 danaugrs

Sorry, I didn't even see WebGlCanvas.

I was already planning to allow FlyControl to work without capturing the mouse, so good news there. =)

Actually, now that I think about it more, I am favoring the 2nd option (let user indicate to control when mouse is or isn't captured). So I think that's what I'll go with for now. I think the last time I played a first-person game (ages ago), there was a key to toggle mouse capture so you could still play "naturally" in windowed mode but still release the cursor to interact with the rest of the computer desktop. My brain latched onto that and wouldn't let go.

I do think it a very good idea to add methods to IWindow to Set + Get the input mode, or at least the cursor mode. When using the mouse to yaw+pitch when windowed, it can be quite jarring to move the cursor out of the window then back in. I guess this might be called "cursor locking" in WebGL (W3C, Unity).

I could probably also submit a separate PR for the modification to IWindow and the respective implementations, if desired, but completely understand if you'd rather do that yourself or leave it to someone more experienced with the whole of G3N. I've used glfw+opengl, but haven't yet used webgl or any of the wasm targeted parts of Go though I imagine I could figure it out.

Thanks for the feedback and info for FlyControl. I have some fine tuning and commenting to do in addition to what we've been discussing, but I should be able to submit a PR fairly soon.

quillaja avatar Nov 05 '21 19:11 quillaja

So much for submitting a PR "fairly soon"! I had most of it done a while ago, but got derailed around the holidays. I'm pretty pleased with how it turned out, though I suppose nothing is 'perfect'. I could submit a PR right now, but I'd prefer if you could take a look at the exposed API, documentation, etc before hand when you have time. I started off with the existing OrbitControl as a template and went from there. Mostly my intent was to provide maximum flexibility via configuration if desired, but provide very simple but functional defaults and options when a "batteries included" control is desired (demos, etc).

Here's a go doc dump of the exported API and documentation, which hopefully describes how to use the FlyControl with sufficient clarity. I didn't follow Go's method naming conventions doggedly because I felt in many cases the verby nature of method names created more natural calls (eg flycontrol.Yaw(0.1)).

The full file is at: https://github.com/quillaja/engine/blob/fly/camera/fly_control.go

package camera // import "github.com/g3n/engine/camera"

type FlyControl struct {
	core.Dispatcher // Embedded event dispatcher

	// Constraints map.
	// Specifies the maxium cumulative value in a particular movement.
	// Translation movements aren't used.
	// Angular rotation contraints are in radians.
	Constraints map[FlyMovement]float32

	// Speeds map.
	// Translation movements in units/event.
	// Angular rotation and zoom in radians/event.
	Speeds map[FlyMovement]float32

	// Keys map.
	// Maps a movment to a key press.
	Keys map[FlyMovement]window.Key

	// Mouse map.
	// Maps a movement to a mouse "guesture"
	// (a combination of mouse movement and optional mouse button).
	Mouse map[FlyMovement]MouseGuesture
	// Has unexported fields.
}
    FlyControl provides a camera controller that allows looking at the scene
    from a first person style view. It allows for translation in the
    Forward/Backward, Right/Left, and Up/Down directions, and rotation in the
    Yaw/Pitch/Roll directions from the camera's point of view. The camera's
    field of view can also be modified to allow ZoomOut/ZoomIn.

    For maximum flexibility, the FlyControl is very configurable, but two main
    modes of operation exist: "manual" and "automatic".

    "Manual" mode requires the user to directly make adjustments to the
    FlyControl's position and orientation by using the Forward(), Right(),
    Yaw(), Pitch() and other similar methods. Manual mode does not subscribe to
    key or mouse events, nor does it use the FlyControl's member maps "Speeds",
    "Keys", or "Mouse" ("Constraints" is used). A FlyControl created (eg with
    NewFlyControl()) with no options defaults to "manual" mode.

    "Automatic" mode subscribes to key and mouse events, and handles position
    and orientation adjustments according to parameters specified in the "Keys",
    "Mouse", "Speed", and "Constraint" maps. Generally, methods such as
    Forward() will not be used. Some preconfigured options are available with
    FPSStyle() and FlightSimStyle(), and can be further configured by the
    With*() options, and by modifying the FlyControl's Keys (etc) maps directly.

func NewFlyControl(cam *Camera, target, worldUp *math32.Vector3,
	options ...FlyControlOption) *FlyControl
    NewFlyControl initalizes a FlyControl to manipulate cam. It starts
    positioned at the cam's position, and oriented looking at target with
    camera's up aligned in the direction of worldUp. Configuration options can
    be provided. The FlyControl will default to "manual" mode if no options are
    given. See documentation for FlyControl for more information about common
    usage patterns.

func (fc *FlyControl) Dispose()
    Dispose unsubscribes from all events.

func (fc *FlyControl) Forward(delta float32)
    Forward and backward translation along the camera's forward axis.

func (fc *FlyControl) GetDirections() (forward, up math32.Vector3)
    GetDirections returns copies of the camera's current "forward" and "up"
    direction. The up direction is from the camera's perspective regardless of
    the status of IsUsingWorldUp().

func (fc *FlyControl) GetMouseSensitivity() float32
    GetMouseSensitivity gets the current mouse sensitivity in [0,1].

func (fc *FlyControl) GetPosition() (position math32.Vector3)
    GetPosition returns a copy of the camera's position.

func (fc *FlyControl) GetRotation() (rotation math32.Vector3)
    GetRotation returns a copy of the camera's cumulative rotation. Yaw, Pitch,
    and Roll are in the X, Y, and Z coordinates.

func (fc *FlyControl) IsUsingWorldUp() bool
    IsUsingWorldUp returns true if the "up" direction used for movement and
    rotation is the world up, and false if it is the camera up.

func (fc *FlyControl) Pitch(delta float32)
    Pitch adjustment in radians. Pitch rotates the camera about its right axis.

    Caution: If using world up, take care not to allow the camera's forward
    direction to become parallel to the world up direction by setting
    constraints on PitchUp and PitchDown to be in the interval (-π/2, π/2). If
    camera forward and world up become parallel, NaNs will happen.

func (fc *FlyControl) Reorient(target, worldUp *math32.Vector3)
    Reorient the camera to look at target and use the given world up direction.

func (fc *FlyControl) Reposition(position *math32.Vector3)
    Reposition the camera to the new position.

func (fc *FlyControl) Right(delta float32)
    Right and left translation along the camera's right axis.

func (fc *FlyControl) Roll(delta float32)
    Roll adjustment in radians. Roll rotates the camera about its forward axis.

func (fc *FlyControl) ScaleZoom(fovScale float32)
    ScaleZoom modifies the fov based on scaling the current fov.

func (fc *FlyControl) SetMouseIsCaptured(captured bool)
    SetMouseIsCaptured is used to inform the FlyControl about the cursor being
    captured by the GL window so the FlyControl can respond to mouse events
    appropriately. Mouse/cursor capture is not modifed by the FlyControl.

func (fc *FlyControl) SetMouseSensitivity(value float32)
    SetMouseSensitivity to value in [0,1]. Values outside of [0,1] are clamped
    to that range.

func (fc *FlyControl) Subscribe(key, mouse bool)
    Subscribe to input events. A value of false for either key or mouse does not
    unsubscribe from that type of event. Multiple calls will subscribe only
    once.

func (fc *FlyControl) Unsubscribe(key, mouse bool)
    Unsubscribe from input events.

func (fc *FlyControl) Up(delta float32)
    Up and down translation along the current "up" axis.

func (fc *FlyControl) UseWorldUp(use bool)
    UseWorldUp sets the FlyControl to use world up instead of camera up when
    calculating movements Up/Down, Yaw, Pitch, and Roll. Setting this to false
    will use the "up" direction relative to the camera's point of view.

    Caution: If using world up, take care not to allow the camera's forward
    direction to become parallel to the world up direction by setting
    constraints on PitchUp and PitchDown to be in the interval (-π/2, π/2). If
    camera forward and world up become parallel, NaNs will happen.

func (fc *FlyControl) Yaw(delta float32)
    Yaw adjustment in radians. Yaw rotates the camera about the current "up"
    axis. Positive yaw is "right" from the camera's point of view.

func (fc *FlyControl) Zoom(delta float32)
    Zoom modifies the fov based on the delta change in radians.

quillaja avatar Feb 17 '22 13:02 quillaja