diff --git a/src/editor.ts b/src/editor.ts index c4f467b5..cbf144ad 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -116,6 +116,57 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S scene.forceRender = true; }); + events.on('splatDisplayToggle', (splatDisplayToggle: boolean) => { + scene.graphicsDevice.scope.resolve('splatDisplayToggle').setValue(!splatDisplayToggle); + scene.forceRender = true; + }); + + events.on('boundingRingToggle', (boundingRingToggle: boolean) => { + scene.graphicsDevice.scope.resolve('boundingRingToggle').setValue(boundingRingToggle); + scene.graphicsDevice.scope.resolve('boundingRingSize').setValue(events.invoke('boundingRingSize')); + scene.forceRender = true; + }); + + events.on('boundingRingSize', (value: number) => { + scene.graphicsDevice.scope.resolve('boundingRingSize').setValue(value); + scene.graphicsDevice.scope.resolve('ringSize').setValue(events.invoke('camera.mode') === 'rings' ? value : 0.0); + scene.forceRender = true; + }); + + events.on('selectedSplatRingsToggle', (selectedSplatRingToggle: boolean) => { + scene.graphicsDevice.scope.resolve('selectedSplatRingsToggle').setValue(selectedSplatRingToggle); + scene.graphicsDevice.scope.resolve('selectedSplatRingsSize').setValue(events.invoke('selectedSplatRingsSize')); + scene.forceRender = true; + }); + + events.on('selectedSplatRingsSize', (value: number) => { + scene.graphicsDevice.scope.resolve('selectedSplatRingsSize').setValue(value); + scene.forceRender = true; + }); + + const centerPointColors = [ + [0, 0, 0, 0.25], + [0, 0, 1.0, 0.5], + [1.0, 1.0, 0.0, 0.5] + ]; + + events.on('centerPointColor', (colorValue: number[]) => { + centerPointColors[1] = colorValue; + scene.graphicsDevice.scope.resolve('centerColors[0]').setValue(centerPointColors.flat()); + scene.forceRender = true; + }); + + events.on('selectedCenterPointColor', (colorValue: number[]) => { + centerPointColors[2] = colorValue; + scene.graphicsDevice.scope.resolve('centerColors[0]').setValue(centerPointColors.flat()); + scene.forceRender = true; + }); + + events.on('selectedSplatColor', (colorValue: number[]) => { + scene.graphicsDevice.scope.resolve('selectedSplatColor').setValue(colorValue); + scene.forceRender = true; + }); + events.on('show.gridOn', () => { scene.grid.visible = true; }); diff --git a/src/main.ts b/src/main.ts index a3a6746c..e567a4cd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -78,6 +78,8 @@ const initShortcuts = (events: Events) => { shortcuts.register(['U', 'u'], { event: 'select.unhide' }); shortcuts.register(['['], { event: 'tool.brushSelection.smaller' }); shortcuts.register([']'], { event: 'tool.brushSelection.bigger' }); + shortcuts.register(['9'], { event: 'tool.brushSelection.smaller' }); + shortcuts.register(['0'], { event: 'tool.brushSelection.bigger' }); shortcuts.register(['Z', 'z'], { event: 'edit.undo', ctrl: true }); shortcuts.register(['Z', 'z'], { event: 'edit.redo', ctrl: true, shift: true }); shortcuts.register(['M', 'm'], { event: 'camera.toggleMode' }); @@ -97,6 +99,63 @@ const initShortcuts = (events: Events) => { } }); + let centerPointColorSave = [0.0,0.0,1.0,0.5]; + events.on('centerPointColor', (colors: number[]) => { + if (colors[3] !== 0) { + centerPointColorSave = colors; + } + }); + + shortcuts.register(['J', 'j'], { + func: () => { + events.fire('centerPointColor', events.invoke('centerPointColor')[3] === 0 ? centerPointColorSave : [0,0,0,0]); + } + }); + + let selectedCenterPointColorSave = [1.0,1.0,0,0.5]; + events.on('selectedCenterPointColor', (colors: number[]) => { + if (colors[3] !== 0) { + selectedCenterPointColorSave = colors; + } + }); + + shortcuts.register(['L', 'l'], { + func: () => { + events.fire('selectedCenterPointColor', events.invoke('selectedCenterPointColor')[3] === 0 ? selectedCenterPointColorSave : [0,0,0,0]); + } + }); + + let selectedSplatColorSave = [1.0,1.0,0,0.5]; + events.on('selectedSplatColor', (colors: number[]) => { + if (colors[3] !== 0) { + selectedSplatColorSave = colors; + } + }); + + shortcuts.register(['K', 'k'], { + func: () => { + events.fire('selectedSplatColor', events.invoke('selectedSplatColor')[3] === 0 ? selectedSplatColorSave : [selectedSplatColorSave[0],selectedSplatColorSave[1],selectedSplatColorSave[2],0]); + } + }); + + shortcuts.register(['Q', 'q'], { + func: () => { + events.fire('selectedSplatRingsToggle', !events.invoke('selectedSplatRingsToggle')); + } + }); + + shortcuts.register(['O', 'o'], { + func: () => { + events.fire('boundingRingToggle', !events.invoke('boundingRingToggle')); + } + }); + + shortcuts.register(['N', 'n'], { + func: () => { + events.fire('splatDisplayToggle', !events.invoke('splatDisplayToggle')); + } + }); + return shortcuts; }; diff --git a/src/scene.ts b/src/scene.ts index 61c0d82c..55f13be8 100644 --- a/src/scene.ts +++ b/src/scene.ts @@ -219,6 +219,9 @@ class Scene { this.add(model); this.camera.focus(); this.events.fire('loaded', filename); + this.events.fire('centerPointColor', this.events.invoke('centerPointColor')); + this.events.fire('selectedCenterPointColor', this.events.invoke('selectedCenterPointColor')); + this.events.fire('selectedSplatColor', this.events.invoke('selectedSplatColor')); } catch (err) { this.events.fire('error', err); } diff --git a/src/splat-debug.ts b/src/splat-debug.ts index cd34bfb9..8f16eecf 100644 --- a/src/splat-debug.ts +++ b/src/splat-debug.ts @@ -21,15 +21,10 @@ uniform mat4 matrix_projection; uniform mat4 matrix_viewProjection; uniform float splatSize; +uniform vec4 centerColors[3]; varying vec4 color; -vec4 colors[3] = vec4[3]( - vec4(0, 0, 0, 0.25), - vec4(0, 0, 1.0, 0.5), - vec4(1.0, 1.0, 0.0, 0.5) -); - void main(void) { int state = int(vertex_position.w); if (state == -1) { @@ -37,7 +32,7 @@ void main(void) { } else { gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position.xyz, 1.0); gl_PointSize = splatSize; - color = colors[state]; + color = vec4(centerColors[state]); } } `; diff --git a/src/splat.ts b/src/splat.ts index f73d711a..c4e6fac2 100644 --- a/src/splat.ts +++ b/src/splat.ts @@ -19,6 +19,7 @@ import { State } from './edit-ops'; const vertexShader = /*glsl*/` uniform sampler2D splatState; +uniform bool splatDisplayToggle; flat varying highp uint vertexState; @@ -28,6 +29,9 @@ flat varying highp uint vertexId; void main(void) { + if (splatDisplayToggle){ + return; + } // evaluate center of the splat in object space vec3 centerLocal = evalCenter(); @@ -55,9 +59,18 @@ flat varying highp uint vertexState; uniform float pickerAlpha; uniform float ringSize; float PI = 3.14159; +uniform vec4 selectedSplatColor; +uniform bool selectedSplatRingsToggle; +uniform float selectedSplatRingsSize; +uniform bool boundingRingToggle; +uniform float boundingRingSize; +uniform bool splatDisplayToggle; void main(void) { + if (splatDisplayToggle){ + discard; + } if ((vertexState & uint(4)) == uint(4)) { // deleted discard; @@ -91,16 +104,31 @@ void main(void) } else { if ((vertexState & uint(1)) == uint(1)) { // selected - c = vec3(1.0, 1.0, 0.0); + c = mix(color.xyz, selectedSplatColor.xyz, selectedSplatColor.w); + alpha = B; + if (selectedSplatRingsToggle){ + if (A < 4.0 - selectedSplatRingsSize / 3.0) { + alpha = max(0.05, B); + } else { + alpha = 0.6; + c = selectedSplatColor.xyz; + } + } } else { // normal c = color.xyz; + alpha = B; + if (boundingRingToggle){ + if (A < 4.0 - boundingRingSize / 3.0) { + alpha = B; + } else { + alpha = 0.6; + } + } } - alpha = B; - if (ringSize > 0.0) { - if (A < 4.0 - ringSize * 4.0) { + if (A < 4.0 - ringSize / 3.0) { alpha = max(0.05, B); } else { alpha = 0.6; @@ -258,10 +286,11 @@ class Splat extends Element { const selected = events.invoke('selection') === this; const cameraMode = events.invoke('camera.mode'); const splatSize = events.invoke('splatSize'); + const boundingRingSize = events.invoke('boundingRingSize'); // configure rings rendering const material = this.root.gsplat.instance.material; - material.setParameter('ringSize', (selected && cameraMode === 'rings' && splatSize > 0) ? 0.04 : 0); + material.setParameter('ringSize', (selected && cameraMode === 'rings') ? boundingRingSize : 0); // render splat centers if (selected && cameraMode === 'centers' && splatSize > 0) { diff --git a/src/style.scss b/src/style.scss index 142f4f96..4043ad87 100644 --- a/src/style.scss +++ b/src/style.scss @@ -47,7 +47,7 @@ body { } #control-panel { - width: 340px; + width: 400px; height: 100%; border-right: 1px solid #20292b; display: flex; @@ -177,7 +177,7 @@ body { } .control-label { - width: 100px; + width: 140px; flex-shrink: 0; flex-grow: 0; line-height: 24px; @@ -265,6 +265,12 @@ body { } } +.pcui-input-element > input{ + width: 100%; + padding: 0 4px; + font-size: 12px; +} + .pcui-vector-input { margin-left: 6px; margin-right: 6px; diff --git a/src/ui/control-panel.ts b/src/ui/control-panel.ts index f1513c03..f9756a86 100644 --- a/src/ui/control-panel.ts +++ b/src/ui/control-panel.ts @@ -1,8 +1,9 @@ -import { BooleanInput, Button, Container, Label, NumericInput, Panel, RadioButton, SelectInput, SliderInput, TreeView, TreeViewItem, VectorInput } from 'pcui'; +import { BooleanInput, Button, ColorPicker, Container, Label, NumericInput, Panel, RadioButton, SelectInput, SliderInput, TreeView, TreeViewItem, VectorInput } from 'pcui'; import { Events } from '../events'; import { Element, ElementType } from '../element'; import { Splat } from '../splat'; import { version as appVersion } from '../../package.json'; +import { Color } from 'playcanvas'; class ControlPanel extends Panel { constructor(events: Events, remoteStorageMode: boolean, args = { }) { @@ -473,6 +474,165 @@ class ControlPanel extends Panel { allData.append(allDataLabel); allData.append(allDataToggle); + // highlighting color of selected splats (splat and ring) + const selectedSplatColor = new Container({ + class: 'control-parent' + }); + + const selectedSplatColorLabel = new Label({ + class: 'control-label', + text: 'Highlight Splat Color' + }); + + const selectedSplatColorPicker = new ColorPicker({ + class: 'control-element-expand', + channels: 4, + value: [1.0,1.0,0.0,0.5] + }); + + selectedSplatColor.append(selectedSplatColorLabel); + selectedSplatColor.append(selectedSplatColorPicker); + + // toggle splats + const splatDisplayToggle = new Container({ + class: 'control-parent' + }); + + const splatDisplayToggleLabel = new Label({ + class: 'control-label', + text: 'Show Splats' + }); + + const splatDisplayToggleCb = new BooleanInput({ + class: 'control-element', + value: true + }); + + splatDisplayToggle.append(splatDisplayToggleLabel); + splatDisplayToggle.append(splatDisplayToggleCb); + + // color of center points + const centerPointColor = new Container({ + class: 'control-parent' + }); + + const centerPointColorLabel = new Label({ + class: 'control-label', + text: 'Center Point Color' + }); + + const centerPointColorPicker = new ColorPicker({ + class: 'control-element-expand', + channels: 4, + value: [0.0,0.0,1.0,0.5] + }); + + centerPointColor.append(centerPointColorLabel); + centerPointColor.append(centerPointColorPicker); + + // highlight color of center points + const selectedCenterPointColor = new Container({ + class: 'control-parent' + }); + + const selectedCenterPointColorLabel = new Label({ + class: 'control-label', + text: 'Highlight Point Color' + }); + + const selectedCenterPointColorPicker = new ColorPicker({ + class: 'control-element-expand', + channels: 4, + value: [1.0,1.0,0.0,0.5] + }); + + selectedCenterPointColor.append(selectedCenterPointColorLabel); + selectedCenterPointColor.append(selectedCenterPointColorPicker); + + // toggle bounding rings + const boundingRingToggle = new Container({ + class: 'control-parent' + }); + + const boundingRingToggleLabel = new Label({ + class: 'control-label', + text: 'Bounding Rings' + }); + + const boundingRingToggleCb = new BooleanInput({ + class: 'control-element', + }); + + boundingRingToggle.append(boundingRingToggleLabel); + boundingRingToggle.append(boundingRingToggleCb); + + // bounding ring thickness + const boundingRingSize = new Container({ + class: 'control-parent' + }); + + const boundingRingSizeLabel = new Label({ + class: 'control-label', + text: 'Bounding Ring Size' + }); + + const boundingRingSizeSlider = new SliderInput({ + class: 'control-element-expand', + precision: 2, + min: 0, + max: 1, + value: 0.5 + }); + + boundingRingSize.append(boundingRingSizeLabel); + boundingRingSize.append(boundingRingSizeSlider); + + // toggle bounding rings for selected splats + const selectedSplatRingsToggle = new Container({ + class: 'control-parent' + }); + + const selectedSplatRingsToggleLabel = new Label({ + class: 'control-label', + text: 'Highlight Splat Rings' + }); + + const selectedSplatRingsToggleCb = new BooleanInput({ + class: 'control-element', + }); + + selectedSplatRingsToggle.append(selectedSplatRingsToggleLabel); + selectedSplatRingsToggle.append(selectedSplatRingsToggleCb); + + // bounding ring thickness + const selectedSplatRingsSize = new Container({ + class: 'control-parent' + }); + + const selectedSplatRingsSizeLabel = new Label({ + class: 'control-label', + text: 'Highlight Ring Size' + }); + + const selectedSplatRingsSizeSlider = new SliderInput({ + class: 'control-element-expand', + precision: 2, + min: 0, + max: 1, + value: 0.5 + }); + + selectedSplatRingsSize.append(selectedSplatRingsSizeLabel); + selectedSplatRingsSize.append(selectedSplatRingsSizeSlider); + + optionsPanel.append(splatDisplayToggle); + optionsPanel.append(centerPointColor); + optionsPanel.append(selectedCenterPointColor); + optionsPanel.append(selectedSplatColor); + optionsPanel.append(boundingRingToggle); + optionsPanel.append(boundingRingSize); + optionsPanel.append(selectedSplatRingsToggle); + optionsPanel.append(selectedSplatRingsSize); optionsPanel.append(allData); // append @@ -593,6 +753,102 @@ class ControlPanel extends Panel { events.fire('splatSize', value); }); + events.on('selectedSplatColor', (value: number[]) => { + selectedSplatColorPicker.value = value; + }); + + events.function('selectedSplatColor', () => { + return selectedSplatColorPicker.value; + }); + + selectedSplatColorPicker.on('change', (value: number[]) => { + events.fire('selectedSplatColor', value); + }); + + events.on('boundingRingToggle', (value: boolean) => { + boundingRingToggleCb.value = value; + }); + + events.function('boundingRingToggle', () => { + return boundingRingToggleCb.value; + }); + + boundingRingToggleCb.on('change', (value: boolean) => { + events.fire('boundingRingToggle', value); + }); + + events.on('boundingRingSize', (value: number) => { + boundingRingSizeSlider.value = value; + }); + + events.function('boundingRingSize', () => { + return boundingRingSizeSlider.value; + }); + + boundingRingSizeSlider.on('change', (value: number) => { + events.fire('boundingRingSize', value); + }); + + events.on('selectedSplatRingsToggle', (value: boolean) => { + selectedSplatRingsToggleCb.value = value; + }); + + events.function('selectedSplatRingsToggle', () => { + return selectedSplatRingsToggleCb.value; + }); + + selectedSplatRingsToggleCb.on('change', (value: boolean) => { + events.fire('selectedSplatRingsToggle', value); + }); + + events.on('selectedSplatRingsSize', (value: number) => { + selectedSplatRingsSizeSlider.value = value; + }); + + events.function('selectedSplatRingsSize', () => { + return selectedSplatRingsSizeSlider.value; + }); + + selectedSplatRingsSizeSlider.on('change', (value: number) => { + events.fire('selectedSplatRingsSize', value); + }); + + events.on('centerPointColor', (value: number[]) => { + centerPointColorPicker.value = value; + }); + + events.function('centerPointColor', () => { + return centerPointColorPicker.value; + }); + + centerPointColorPicker.on('change', (value: number[]) => { + events.fire('centerPointColor', value); + }); + + events.on('splatDisplayToggle', (value: boolean) => { + splatDisplayToggleCb.value = value; + }); + + events.function('splatDisplayToggle', () => { + return splatDisplayToggleCb.value; + }); + + splatDisplayToggleCb.on('change', (value: boolean) => { + events.fire('splatDisplayToggle', value); + }); + + events.on('selectedCenterPointColor', (value: number[]) => { + selectedCenterPointColorPicker.value = value; + }); + + events.function('selectedCenterPointColor', () => { + return selectedCenterPointColorPicker.value; + }); + + selectedCenterPointColorPicker.on('change', (value: number[]) => { + events.fire('selectedCenterPointColor', value); + }); + focusButton.on('click', () => { events.fire('camera.focus'); }); diff --git a/src/ui/shortcuts-popup.ts b/src/ui/shortcuts-popup.ts index 119a0211..b0249e05 100644 --- a/src/ui/shortcuts-popup.ts +++ b/src/ui/shortcuts-popup.ts @@ -8,7 +8,7 @@ const shortcutList = [ { key: 'R', action: 'Rect Selection' }, { key: 'B', action: 'Brush Selection' }, { key: 'P', action: 'Picker Selection' }, - { key: '[ ]', action: 'Decrease/Increase brush size' }, + { key: '[ ] OR 9 0', action: 'Decrease/Increase brush size' }, { key: 'Esc', action: 'Deactivate Tool' }, { header: 'SELECTION' }, { key: 'A', action: 'Select All' }, @@ -25,6 +25,12 @@ const shortcutList = [ { key: 'Ctrl + Z', action: 'Undo' }, { key: 'Ctrl + Shift + Z', action: 'Redo' }, { key: 'Space', action: 'Toggle Debug Splat Display' }, + { key: 'J', action: 'Toggle center points alpha' }, + { key: 'L', action: 'Toggle selected points alpha' }, + { key: 'K', action: 'Toggle highlighting color' }, + { key: 'Q', action: 'Toggle highlight rings' }, + { key: 'O', action: 'Toggle bounding rings' }, + { key: 'N', action: 'Toggle splats' }, { key: 'F', action: 'Focus Camera on current selection' }, { key: 'M', action: 'Toggle Camera Mode'}, { key: 'G', action: 'Toggle Grid' },