As of now, the ThreeJS library provides the ability to use one of its 8 control “addons” (Arcball, Drag, FirstPerson, Fly, Orbit, PointerLock, Trackball and Transform). In my project, I allow the user to choose one of these to handle stuff in the scene, by disposing the “old” control and recreating the “new” one. As a side note, some time ago, one of the ThreeJS developers recommended that I should use the .enabled property of the control instead of my disposing + recreation system, but of course, although the idea was correct in theory, in his infinite wisdom he forgot to consider that not all controls have this property (e.g. Fly or PointerLock), so I kept my working system despite the advice.
Now, everything works as it should with this system, except one thing: certain controls (e.g. Arcball, Orbit, Trackball, not sure if others as well) “reset” the camera to what they see as an initial state during initialization, basically offsetting the current camera state on switching from one control to another, especially if some panning has been performed using the “old” control before the said switch to the “new” one. Since I’m not able to use the .saveState() and .reset() methods of the controls to correct this behavior (because the “old” control is destroyed before the switch is performed in my system), I need to “manually” save the camera state before disposing the old control and then restoring it after the creation of the new one.
So, how do I do this in a way that works with any of those 8 controls? Normally, it would probably involve saving the camera’s world matrix in order to be loaded again later, but then some SO answers (obviously obsolete by now because of the library’s lack of backward compatibility) mention having to set .autoUpdateMatrix to false before it, others mention the need to save and restore the target / center of rotation as well for some controls, others say that for some controls the camera angle is set by the camera.quaternion and not by the camera.rotation, and of course none of them cover all of the controls since most folks use only one control for their limited case.
How do I navigate through all this mess and come up with a solution that fits all 8 cases? Yeah, sorry, no fiddle this time, I’m tired of with making the answer easy for someone else when everything is made harder for me and others by the lack of consistency in the platform. For the record, I don’t care about the “old” control’s specific settings, only the camera related ones.
if you’re “too tired” to try to resolve your own use case i’m not sure i see why anyone else would put the energy needed to resolve the issue for you, that’s simply not how things work. I’d be happy to look into a live example to help with a solution to this but to merely offload a lack of enthusiasm towards a perpetually more stable version of the library and pass it off as “inconsitencies that are making it harder for you” sounds ridiculous and is frankly lazy.
there are a few approaches you could try to navigate towards a workable solution, as i say, i’d be willing to look at a live example to test some ideas, such as possibly creating a global THREE.Matrix4(), copying the camera matrix to this global reference before disposing the controls with the Matrix4.copy method and then copying it back to the camera after the new controls have been created, the only problem i could see with this is that some controls and the respective camera may need to be updated via controls.object.
this also sounds like a reasonable approach although i’m sure not all controls have a .target property, without a live example it’s just too much energy to expect someone to setup and test for you.
I do not have experience with all the controls, but it is reasonable to expect that they differ more or less.
Handling transition from any of the N controls to any other of the N controls can be solved similar to how texts are translated between any two of N languages.
My suggestion is to add two methods for each control (either by introducing your own classes that extend the controls’ classes, or by modifying their sources). One of the method is getState and the other is setState. In this way you can easily switch from any controls to any other controls.
The most crucial thing is to implement these two methods. I believe that because of the differences between the controls, their implementations might differ. And this is unavoidable.
Sorry, but legitimate criticism is always healthy, especially when thousands of posts both here and on SO make these things obvious. I’m not going to turn a blind eye to things and always speak well of something just because the reality is inconvenient for those that choose to ignore the problems.
As for the live example, what I meant that if one knows what the solution to a (simpler) problem is, he doesn’t necessarily need a live example demostrating the problem in order to provide an answer. Take into account that those asking are already in an inferior position when it comes to knowledge, and they are the ones that actually spent time trying to make something work, so calling them lazy and ridiculous for not spending yet more time to assemble a live example for the one that answers is not cool - just saying.
That being said, I know the value of live examples, especially when the problem hasn’t been encountered before, like it might be the case here. I’ll try providing one either today or tomorrow, because as you can imagine, simplifying a more complex project to emphasise a problem is not exactly a walk in the park.
Already tried that:
var cameramatrix = camera.matrixWorld.clone();
...
[dispose old control and create the new one]
...
camera.matrixWorld.copy(cameramatrix);
and didn’t seem to work. I tried adding camera.updateMatrixWorld(true); or camera.matrixWorldNeedsUpdate = true; to no avail, it still jumps back to the “0,0,0” initial position when changing from, say, Arcball to Orbit.
Not sure if it helps, but until I’ll manage to assemble a live example, this is how I approach things in the original code:
The zapItem() disposes stuff, atMenuWheel() is where I switch between variants (aka w[style]) of objects like controls and others on mouse wheel scrolling, and setPageControls() is where I create a control according to w.controls in this case. The various setSomething() are just standard ways of creating 3D objects in my code, and propagate() is more or less a single step of the animation loop, including but not limited to rendering the whole thing.
Thanks for answering. I hoped that a simpler matrix save and load would solve the issue, but apparently it does not. I would like to avoid modifying the source code as much as possible. I’ll consider your suggestions though, much appreciated.
i’m not sure what you’re referring to here… you’re attempting to build an environment using all of the various singular control classes, that’s the reality. I’d be keen to know of any software that implements the functionality you’re asking for ootb but in what seems like a very very rare use case i personally don’t think it’s fair to assume that it’s up to anyone else to account for combining and interweaving already quite clearly complex methods to make things “easier” in such a rare case, all but two of these classes already have an enabled property, of those two, one of them uses the pointerLockAPI. This simply may have not been considered as a practical application in three as of yet and you may be on to something groundbreaking, if so, i’m sure a PR request of your own that accounts for interchangability of all control types in this way would be greatly appreciated.
Yes, boiling the case down to it’s bare bones is best, not only to help recognise the fundamental patterns required to find the most straight forward solution but also to help others help you in trying to resolve a problematic use case, the more minimal an example concerning the problem, the easier an answer is to find for that particular problem.
No but to be considerate of the fact that untangling and making sense of uncontextualized patches in your application, building a live working environment from that as well as help you find a resolve is even more of a headache for those willing to use their time helping you than it is for you to attempt a solution alone, you’re reasonable enough to see that right?
give it a shot, it’d definitely make finding a solution easier / possible
The subject both you and I referred to when talking about the “reality” earlier was about the inconsistencies and a perpetually more stable version of the library (which is a fact), it was not about this particular case of using all controls selectively. When it comes to the latter, as far as I know, if something is rare, its value increases and it’s not a reason to dismiss it just because 99% of folks are happy to settle for less and use things “the way they should”. If all people thought this way, most of the great things we have - including, with all its faults, ThreeJS - would have never seen the light of day.
As for a PR, remember CameraControls.js, that tried to do integrate all controls into one, comprehensive, multifunctional unit? Can you tell me where is it now? Oh wait … in the recycle bin, cause it was not considered “practical”. Why bothering to convince those who already took such a decision of the contrary, and arguing for something they already closed their minds to? At best, I’ll get only rejection on various grounds and receive a similar response to yours earlier (for a much more obvious and general issue). I don’t need that kind of quarrel, really…
I more or less agree with you on the rest - but like I said, I thought an easy solution along the lines of matrix preservation was available and easy to share by someone more knowledgable, and would not require the hassle of creating a live example just for two or three lines of code that should have been common knowledge.
Anyway, here is a (slightly incomplete, since for simplicity I only set up Arcball, Fly and Orbit controls to work, the rest is commented in the createcontrols() function) fiddle that emphasizes the problem I’m having:
run it
click on the canvas to show the mesh (yeah, I know the mesh doesn’t show up right away because some things are not included in the texture loader callback, but then I preload my resources in my project, so I’m not having the problem)
press on numpad+ to show the control index in the two panels at the top
zoom out a bit and then pan the mesh to one of the canvas corners via right click dragging
press on numpad* to switch to another control via disposing and creating the new one
do the above till you get to the 5th control aka Orbit and see how the mesh jumps back to the center
So, this happens even though I save the camera’s old world matrix before disposing the old control and then restore it after the new control has been created in the modcontrols() function. It happens whenever either the Arcball or Orbit control is the “new” control that is being switched to. The movements or panning done in either control, including the Fly one, are canceled when switching to the 1st (Arcball) or the 5th (Orbit) control in the list. Granted, the code in modcontrols() is simplistic and probably misses some things, but how can I successfully save and restore the camera state between controls though?
Hopefully all the “requirements” are now met for an answer to this, given the live example and all. Looking forward to see the outcome of complying to the demands of the forum.
Is difficult to maintain when all individual controls have their own relative maintenance updates (again feel free to make a PR and actively maintain a class such as this, considering ArcballControls has over 3200 lines of code alone, you could only imagine the problematic stress of maintaining a class that can be actively updated and function in the way you describe)
Is very rarely, if at all, required by 99.9% of users
Would not be modular and can be composed from the easier maintained and modular individual controls classes by a user that does require this type of functionality.
I think you’ve got the wrong end of the stick here, if people concerned themselves with spending all their time on trying to maintain an ever growing “brainstorm” of sketches where absolutely nothing is manageable, nothing is refined, no blueprint is ever distilled, nothing would ever advance, leaving you with an unusable, clunky, indescribable mess of an idea.
i can tell you seem to be in a bit of a pickle over the logic on this but in all fairness the only “demands” here are coming from you about how maintainers of the library don’t seem to be catering to your 0.001% use case, take a chill, try to be understanding of peoples time, knowledge, resources, make it easy for people to help and people will be more inclined to help you, who know’s, you may just get some answers.
In your demo the switch case never increments, nonetheless, the main logic missing in your code is that both ArcballControls and OrbitControls generate a new THREE.Vector3() as a target property on the creation of those particular controls, this needs to be calculated and set to achieve what you’re after, otherwise, of course, your controls.target will always be a new THREE.Vector3() with the default values (0,0,0).
In using the logic from this post you can calculate the controls.target before destroying the previous set of controls. By first calculating the forward vector of the camera and adding that to the camera’s position, you can then use controls.target.copy(lookat) when generating either of those control types in your switch case, feel free to take a look through this logic in the fiddle provided above ^^^ and let us know if this makes sense to what you’re trying to achieve…
First, thank you for your time and interest in this. The solution is on the right track, but not there just yet, and you can see what I’m talking about by checking this sligthly updated fiddle, where I added a listener for the other keys available in FlyControls, especially those regarding the camera rotation around its Z axis, aka key Q and E. What happens is that while it works perfectly for the Arcball, Fly and Orbit controls for the panning situation, when a rotation is performed using Q and E in FlyControls (after previously getting closer with Arcball), going back to either Arcball or Orbit results in a partially rotated result as well:
different from the original and the expected result:
I have no idea why this happens, since the whole process as you explained it makes perfect sense, and the quaternion should handle this properly. I tried to use the .getWorld[Position/Quaternion]() methods in my actual project in the hope that it would solve this little discrepancy, but had no luck with it. It’s a bit annoying since it’s clear that the process should yield the correct outcome in all cases, and the answer to make it right seems quite close. Any ideas? If not, I’ll try to figure it out myself and not bother you with this anymore - I don’t like to seem overdemanding, it already feels like I pushed you to do things you weren’t inclined to do because of my stance on related things.
As for the side talk, it’s clear we have different opinions about how things should be handled: you defend the library’s contributors’ perspective, I defend the end user’s one, and unfortunately, despite all the talk about making things easier for the latter ones, they are incompatible. There is no difference between an open source project and a closed source in that regard, once the former gets big enough, it’s how it is, power corrupts everywhere.
There are all kinds of expectations from the end user which you already mentioned, but when it comes to the other side such expectations are either incorrect, exaggerate, niche cases or lack of involvement in the project. The understanding and appreciation for the time and effort is a one way road, it never gets considered for the end user. More on this would be pointless if it wasn’t understood by now, so I’ll stop here with it.
P.S. In case it wasn’t clear, I didn’t necessarily meant that all controls should be merged into one big file and break modularity - I only used that as an example earlier, to showcase how a similar idea failed to resonate. What I meant was that the controls (in this case) should be able to work seamlessly along (or in place of) each other without the need for additional code to facilitate it.
EDIT: Just a guess, the Q/E issue after using it with FlyControls could have something to do with either the camera or the object up vector, but neither of them seem to change when logging. I’ll look into it and the FlyControls code later today to see what parameters change and need to be replicated in the other controls. I’ll like your answer anyway, since it definitely helped in getting much closer to achieve what I wanted. Thanks again.
it’s odd, the latest the fiddle you shared still doesn’t increment the switch case so it’s difficult to tell what’s actually happening.
EDIT: OK i managed to edit the fiddle to increment the switch case.This is exactly the issue when switching between FlyControls and other camera controls… FlyControls creates rotations on the z axis of the camera when using Q & E keys whereas the other sets of camera controls are fundamentally based on having a constant up vector for the z axis whereby the z axis is prevented from being rotated, they internally use the lookAt method…
i’m not sure there’s going to be an easy solution for the switch from FlyControls to others, one option could be is that you smoothly slerp the resulting camera quaternion from FlyControls back to being “z up” before generating the next set…
Ah, I see. I will continue to investigate how this can be done, this should be possible nevertheless, it’s just that the way to do it apparently requires a deeper knowledge of how these controls work (maybe some undocumented variable that needs to be set, the way I’ve set the controls._up0 one for Arcball in createcontrols(), which was also possible due to the advice of one of the library developers, by the way).
Regarding not incrementing the switch case, I wanted to tell you before but I didn’t, I probably don’t understand what switch case you’re referring to, because for me, the switch case is always incremented / decremented according to the numpad / and * keys being pressed, and this is confirmed by the console.log() lines you put there as helpers:
The fact that you don’t see that might be because you’re looking to press numpad - and + instead? The latter were included in the code because it was originally taken from another of my fiddles where I had more “keys” of the v object, besides controls. In other words, - and + are for navigating through the keys of v (currently only controls), and / and * are for modifying the value of the current key (e.g. switch from one control to another in this case). For the record, I always click once on the canvas and then once on either - or + before navigating between controls via / and *, maybe that helps…
P.S. As a side note, from what I can tell, this might be an issue that happens even if one is not switching between controls using my dispose+create method, and is using the selective .enabled method recommended elsewhere (if that would exist for FlyControls, that is). Now that I think about it, maybe this is the reason why it doesn’t exist in the first place. In any case, an equivalent between a constant up vector along some rotation and a variable up vector along some other rotation should exist, to easily “convert” between one and the other - just saying.
Ok, so after checking the Fly, Orbit and Trackball controls code (Arcball is my favorite, but it’s true that it’s just too big to understand), trying stuff to no avail and lots of exasperation in the process, I got to something that seems to work, at least up until now for what I tried.
Basically, I also rotate the camera.up vector with the difference between the desired (aka saved from the “old” control) rotation and the current (aka existing in the “new” control) rotation, via quaternions (looks like I need to use them, so ditched the matrices). The fiddle that showcases the result is here.
Eventually, I decided to unmark @Lawrence3DPK’s answer as the solution since although he contributed with 87.5% to solve this (7 controls out of 8), there were still 12.5% not solved (i.e. FlyControls). I upvoted his answer instead to show my gratitude for leading me on the right path, although I would have very much preferred to let his answer be the solution and avoid a whole day of messing with this alone.
I won’t mark my answer as the solution yet, because I need to test it thoroughly in my actual project and make sure it works for every possible scenario, so if anyone has a better idea or a more compact and clean way to do this, feel free to post it and I’ll be happy to consider it. EDIT: Nevermind, see below.
In any case, I still believe that a 3D creator using the library should not spend his own time navigating through the library’s code looking to solve what is a consequence of how it is written (or make a PR every time something is not achievable), for a legitimate purpose like, in this case, switching between any of the camera controls at will, because at this rate he’ll spend more time debugging the library than writing his own project…
three is an opensource project that some of the most advanced technicians give their free time towards developing dude, you make it sound like you’re on a premium monthly priced plan. All the while expecting someone else to do what you said “you shouldn’t have to do” …
Have a look at the limitations section of the license and try to get some bearings.
I got it: I’m the lazy, ridiculous, niche case, ignorant and overdemanding dude (according to your own description), all that because I didn’t praise the library and the advanced technicians who developed it for free, despite having a solid argument related to the seamless interaction between parts of it. Meanwhile, I never resorted to ad hominem… techniques, must be too old school for this.
My time and energy are less important than others’ and I should be the one deciphering the inner workings of the platform to solve problems, not the ones who wrote it. My case is the irrelevant 0.001% that can be disregarded, despite the fact that the general use case is precisely the sum of these seemingly minor cases that each user - me included - encounters.
It turns out that the problem was never the fact that I didn’t provide a live example to save the time and energy of others at the expense of my own (which I did, admitting that maybe I was wrong initially), it’s the fact that I can look with a critical eye to the state of this project and not shower praise on it blindly. This seems to be true even when I release others of their inclination to help and successfully manage to continue on the graciously advised path to a satisfactory resolution (which wasn’t even checked).
I’ll keep that in mind next time I’ll consider whether or not to submit a bug or make my opinion heard for other similar open source projects like Chromium, Firefox, VLC, Linux and so on - they might be uncomfortable with that too and rebuff concerns that could lead to their projects’ improvement. No need to get defensive with the license limitations, I’m not a malicious lawyer to sue anyone, I’m just an user who wants the library to get better, even if I’m criticizing, since curiously there’s one side of me that loves it, albeit the other side wants to give up the challenge.
It probably doesn’t matter and I don’t want to boast about it, but I’ve been programming in the hundreds of thousands of lines myself in about a dozen languages since the early DOS days when GitHub wasn’t even born, I know what modularity, open source and good software is and I’ve never took a penny for my work either. I’ve been in your place with thousands of posts on the helping side for other open source projects like Rainmeter, where a much smaller team than here fixes bugs on the same day, is always polite even when saying no and understands the users’ stance. But yeah, sure, I’m the bad guy (sorry - dude) here. SMH.
EDIT: There you go, I marked your post as the solution again, if that was the problem. I hope you’re happy. I won’t be asking for help here anytime soon, I’ll just try to deal with the now broken StackOverflow hints. Thank you for your time and patience. Peace.
It probably would, I changed it mainly as a consequence of the side talk. So far I tested it out in my actual project (where literally everything is disposable and able to be recreated under a different method or choice, including the controls) and so far so good, I’m quite satisfied with the result, and your contribution has been major (I would have never found the link you mentioned or make the connection without your help).
In fact, you hinted to how a solution would look for FlyControls as well in one of your following posts, it was just that the implementation and necessary adjustments are found in my post. So, in that regard, it isn’t entirely without reason to mark your answer either - someone looking to solve this would see both posts anyway. The only thing I’m wondering is whether modifying the camera up direction doesn’t have any other, so far unseen consequences that I’m not aware of. It doesn’t look like it, but it’s a possibility.
Regarding the use case, I’m not 100% sure, but I don’t see why it wouldn’t work for the .enabled switch method as well, if Fly would have one. The camera shot stays the same whatever the transformations and the reactions / settings after resuming them with the “new” control look logical.