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

Initial editor #49

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

MarcusAndreasSvensson
Copy link

Hey,

I made a potential initial version of an editor class and added a tool for acquiring the up vector by selecting three points.
Plan is to add more tool for editing the scene further.

Out of pure necessity for my old computers sake I added an option for an on-demand frame loop which is updated with the invalidate method. Attached the invalidation to the controls and some other events to not be noticeable.

Please let me know if you would like code to be structured in some other way.

@mkkellogg
Copy link
Owner

Cool! I've been thinking about adding some editing functionality for a while; I'll take a look!

@mkkellogg
Copy link
Owner

I'm still looking things over but I do have a few thoughts I want to share up-front.

I do want to transition to more of a proper release-based approach to development, which means I would like to only merge features into main that are fairly complete (and I'm thinking about a separate dev branch as well). I know development on something like an editor will be more open-ended, but I think we can break it up into smaller, well defined features. So in this case it might make sense to create another feature branch off of main in this repo, and we can work on that until we think it's ready to be merged into main.

I really like the idea of selecting three points to define a camera-up vector, but I think at the very least we need to have a nicely styled menu and some visuals/3D-markers in the scene that indicate where the user has selected those points. I also think that we need a better way to override the handling of mouse and keyboard events in the Viewer, which makes me think that Editor might be better off inheriting from Viewer (although I'm still thinking about that).

Another thing to keep in mind is that we're still trying to figure out a good format for .splat files and I think users will have the expectation to save any changes they make in the editor to a file. I'm not sure yet if we should hold off on releasing any editor-related stuff until that format is more finalized (or maybe we just make it clear to the user that changes made in the editor cannot be saved for now).

Since the editor will probably be a fairly large feature (eventually), I just want to make sure we think about these kinds of things ahead of time and get the design right (or close) up-front.

Copy link
Owner

@mkkellogg mkkellogg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finished an initial pass, let me know your thoughts.

@@ -1,3 +1,5 @@
dist
build
node_modules
.vscode/**/*
demo/assets/data/**/*
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add this? The demo directory is really meant to be treated as a source code directory (which means maybe it should actually be moved under /src). Would it be possible to use build/demo/assets/data for the same purpose?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not really, I just didn't want to accidentally commit unnecessary files.

Not sure if it's appropriate with the build/demo/assets/data-route. I would say it depends on what the goal/vision is, which I can't say I see clearly.

const viewer = new GaussianSplats3D.Viewer(viewerOptions);

let editor;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this variable used anywhere?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well no, not really. This would be a non-issue if we were to go with the inheritance route.

const viewerOptions = {
'cameraUp': cameraUpArray,
'initialCameraPosition': cameraPositionArray,
'initialCameraLookAt': cameraLookAtArray
'initialCameraLookAt': cameraLookAtArray,
'frameloop': 'demand',
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make this an enum.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!


const planeFinderLabel = document.createElement('p');
planeFinderLabel.id = 'planeFinderLabel';
planeFinderLabel.innerHTML =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this code in multiple places, maybe it's worth making an updateCameraUpLabel() function (or something like that).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there are a few places where we could refactor to separate functions


setupPlaneFinder() {
if (this.viewer.useBuiltInControls) {
this.viewer.rootElement.removeEventListener('pointerup', this.viewer.pointerUpHandler);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely think we need a less intrusive way of overriding behavior in Viewer. I'm still not sure we should simply have Editor inherit from Viewer and just override the onMouseDown() etc. functions, or if we should add functions to Viewer to allow them to be set by an external source.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm mainly a React developer and am not all that familiar with best practices in vanilla and OOP.This could be a case of lack of knowledge on my part.

The effect I was going for was to have a default global state that is the same as just the viewer. I couldn't really figure out how to do it with inheritance + overriding without loosing the default state. In my mind we had to store those handlers away anyhow and I figured that we might aswell keep the class itself intact as that stored default state.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I was thinking something along the lines of:

class Viewer {

    constructor() {
        window.addEventListener("mousedown", this.onMouseDown.bind(this));
    }

    onMouseDown() {
        // default behavior
    }
}


const PointerMode = {
    Default: 0,
    CameraUpSelection: 1
};

class Editor extends Viewer {

    constructor() {
        super();
        this.pointerMode = PointerMode.Default;
        this.setupEditPanel();
    }

    setupEditPanel() {
        const planeFinderButton = document.createElement('button');
        planeFinderButton.innerHTML = 'Find ground plane';
        planeFinderButton.addEventListener('click', () => {
            this.pointerMode = PointerMode.CameraUpSelection;
        });
    }

    // override Viewer.onMouseDown()
    onMouseDown() {
        if (this.pointerMode === PointerMode.CameraUpSelection) {
            // Do custom stuff for camera up selection
        } else {
            super.onMouseDown();
        }
    }  

}

This would retain default behavior when not selecting the camera-up vector. (This code probably contains errors, but hopefully it conveys the idea :) )

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is absolutely more clear, I like it.

With this structure I would prefer to inherit the Viewer class.

I also think a user should be able to jack into the events and extend the functionality in a straight forward manner. I'm not really sure how one would do that right now, maybe something like how I bound the invalidate() method to the controllers change event. Is there something with this approach that would hinder that?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think an inheritance based approach will hinder that; we can still extend functions in Viewer by overriding them with custom functionality and making sure to call the base function along with it. We have more options that way because we can override any function and it's cleaner because it doesn't require changes to Viewer. I do agree that it would be nice for Viewer to have a general way to hook into events so that anyone developing with the library could hook into them. Maybe we need to make Viewer extend an EventDispatcher class and emit events at the appropriate places.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on making Viewer extend EventDispatcher

return function(mouse) {
clickOffset.copy(this.viewer.mousePosition).sub(this.viewer.mouseDownPosition);
const mouseUpTime = getCurrentTime();
const wasClick = mouseUpTime - this.viewer.mouseDownTime < 0.5 && clickOffset.length() < 2;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to change Viewer.onMouseUp() to only do these calculations to determine if there was a click, and define a new function Viewer.onClick() and call that from Viewer.onMouseUp(). Then we could just override Viewer.onClick() and not have to replicate the click detection logic.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

@@ -125,11 +134,13 @@ export class Viewer {
this.controls.maxPolarAngle = Math.PI * .75;
this.controls.minPolarAngle = 0.1;
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.05;
this.controls.dampingFactor = 0.25;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for this change? If the editor needs higher damping, maybe we can make it a parameter to the Viewer.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the damping is a little lower the "demand" rendering has a much quicker effect. I little of a personal preference, but I also think it's easier to work with in an edit mode, when you want more control of the angles. With that said, this change should maybe be inside the Editor class if we decide to keep it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding if we should make it a parameter to the Viewer class I think we can keep it as it is and let the users reach into the viewer to modify the controls and camera. In my experience it's pretty common practice when working with three. Maybe we could mark them as public for clarity.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that this should be set from within the Editor class. Maybe a parameter to the Viewer class doesn't quite make sense. Also, we should be careful about creating too many parameters for Viewer

@@ -125,11 +134,13 @@ export class Viewer {
this.controls.maxPolarAngle = Math.PI * .75;
this.controls.minPolarAngle = 0.1;
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.05;
this.controls.dampingFactor = 0.25;
this.controls.zoomToCursor = true;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also curious why this setting was changed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing as previous, a little more control when trying to get specific angles when editing. Also this one should be inside the Editor class though.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make a toggle in the Editor UI for this?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely

this.loadingSpinner = new LoadingSpinner();
this.loadingSpinner.hide();

this.viewerNeedsUpdate = true;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we call this flag viewNeedsRefresh ? Just so it's clear that just the view/screen needs to be updated.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I was just kind of going for the "viewMatrixNeedsUpdate"-feel, but I don't have any hard stance on either

@MarcusAndreasSvensson
Copy link
Author

A thought about the ability to edit splats and saving the result.

I do think we can go ahead and do the exports just as you can do on the starting page. If we decide to not save changes in the editor I think we should also remove that functionality from the start page and keep them in sync.

At the very least I think it should be exportable to the original .ply format, cause that's going to be somewhat relevant for a while.

@MarcusAndreasSvensson
Copy link
Author

Adding to the UI/UX concerns you lifted about the up-finder. I've been thinking about adding a button to flip the normal, as it is sensitive to the winding of the points which might not be clear for everyone.

@mkkellogg
Copy link
Owner

Good call on consistency between exporting .splat files on the main demo page and from the editor. At this point I suppose we can just add the same exporting capability to the editor. When the time comes to implement the universal .splat format we're currently designing we'll make sure to notify users (or maybe even retain support for importing the old format?).

As for flipping the camera-up normal, I think a button is a good idea. I didn't even think about winding-order problems :)

@MarcusAndreasSvensson
Copy link
Author

MarcusAndreasSvensson commented Nov 11, 2023

I think it would be a good idea to keep support for the older export. If we decide to do that we should somehow store version info in a header or something in the file.

To me it feels a little over the top when the format is not set at all. On the other hand I would get very annoyed if my previously exported file would work nowhere all of a sudden with no option to re-export it to a newer format.

edit:
I see I contradict myself in this comment, and it reflects my feelings about this pretty closely

@MarcusAndreasSvensson
Copy link
Author

I feel we have something to work with now here after all discussions. I'm going to sit down and work on an updated version in 1 or 2 days and go for a second round of review.

Anything you'd like to add before I dig in?

@mkkellogg
Copy link
Owner

Nothing comes to mind right now, I'll let you know if I think of anything :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants