From e5162744c6f4b890990d4dec920ab6b6b65ad3c4 Mon Sep 17 00:00:00 2001 From: Atsushi Watanabe Date: Wed, 20 Nov 2024 21:26:02 +0900 Subject: [PATCH] Support large pcd (#259) - Fix large map (>30M points) rendering on Firefox - Reduce number of points to render while moving the viewpoint --- .github/workflows/ci.yaml | 2 +- README.md | 76 ++-- command.go | 16 +- console.go | 10 + go.mod | 4 +- main_js.go | 728 ++++++++++++++++++++------------------ view_js.go | 4 + 7 files changed, 451 insertions(+), 389 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e6c37a9..c824361 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '1.18' + go-version: '1.23' - name: Enable pnpm run: corepack enable pnpm - uses: actions/setup-node@v4 diff --git a/README.md b/README.md index 902e419..4ce6a65 100644 --- a/README.md +++ b/README.md @@ -90,43 +90,45 @@ Enter | 貼り付けの確定 ### コマンド操作 -コマンド | 動作 ------------------------------ | ------------------------------------------------------- -cursor | 選択中の点の一覧を表示 (`ID` `X` `Y` `Z`) [\*1](#footnoteKey1) -cursor `X` `Y` `Z` | 新しい点(`X`, `Y`, `Z`)を選択 -cursor `ID` `X` `Y` `Z` | 指定した `ID` の選択中の点の座標を(`X`, `Y`, `Z`)に設定 -unset\_cursor | 点の選択を解除 -select\_range | 選択領域厚を表示 (`R`) [\*1](#footnoteKey1) -select\_range `R` | 選択領域厚を `R` \[メートル\]に設定 -snap\_v | 3点目を垂直スナップ -snap\_h | 2, 3点目を水平スナップ -translate\_cursor `X` `Y` `Z` | 選択中の点を平行移動 -add\_surface | 面作成 -add\_surface `R` | 面作成 (点の間隔 `R` \[メートル\]) -delete | 削除 -label `L` | ラベル設定 (`L`) -undo | Undo -max\_history | Undo回数を表示 -max\_history `A` | Undo回数を設定 (`A`: 0-) -crop | 表示範囲を選択範囲に限定 (無選択での場合は解除) -map\_alpha | 2Dマップの透明度を表示 (`A`) [\*1](#footnoteKey1) -map\_alpha `A` | 2Dマップの透明度を設定 (`A`: 0-1) -voxel\_grid | VoxelGridフィルタで点数を削減 -voxel\_grid `R` | VoxelGridフィルタで点数を削減 (voxelサイズ `R` \[メートル\]) -z\_range | 色をつけるZ座標の範囲を表示 [\*1](#footnoteKey1) -z\_range `Min` `Max` | 色をつけるZ座標の範囲を `Min` - `Max` \[メートル\]に設定 -perspective | 透視投影モード -ortho | 正投影モード -point\_size | 点の表示サイズを表示 [\*1](#footnoteKey1) -point\_size `Size` | 点の表示サイズを `Size` に設定 -segmentation\_param | セグメンテーション時の分離距離を表示 [\*1](#footnoteKey1) -segmentation\_param `D` `R` | セグメンテーション時の分離距離を `D` \[メートル\]、適用範囲を `R` \[メートル\]に設定 -fit\_inserting `AXIS`... | 貼り付け中の点群を既存の点群に位置合わせ [\*2](#footnoteKey2) (位置合わせを行う軸 `AXIS` をスペース区切りで複数指定 [\*3](#footnoteKey3)) -label_segmentation\_param | ラベルを元にしてのセグメンテーション時の範囲と隣接する点群の最大距離を表示 [\*1](#footnoteKey1) -label_segmentation\_param `D` `R` | ラベルを元にしてのセグメンテーション時の隣接する点群の最大距離を `D` \[メートル\]、範囲を `R` \[メートル\]に設定 -render_label_range `Min` `Max` | `Min` - `Max`の範囲内のラベルのみに色をつけて表示 -relabel `Min` `Max` `New` | `Min` - `Max`の範囲内のラベルを`New`値に設定 -unlabel `label1` `label2` `...` | `label1, label2, ...`以外のラベルを`0`に設定 +コマンド | 動作 +---------------------------------- | ------------------------------------------------------- +cursor | 選択中の点の一覧を表示 (`ID` `X` `Y` `Z`) [\*1](#footnoteKey1) +cursor `X` `Y` `Z` | 新しい点(`X`, `Y`, `Z`)を選択 +cursor `ID` `X` `Y` `Z` | 指定した `ID` の選択中の点の座標を(`X`, `Y`, `Z`)に設定 +unset\_cursor | 点の選択を解除 +select\_range | 選択領域厚を表示 (`R`) [\*1](#footnoteKey1) +select\_range `R` | 選択領域厚を `R` \[メートル\]に設定 +snap\_v | 3点目を垂直スナップ +snap\_h | 2, 3点目を水平スナップ +translate\_cursor `X` `Y` `Z` | 選択中の点を平行移動 +add\_surface | 面作成 +add\_surface `R` | 面作成 (点の間隔 `R` \[メートル\]) +delete | 削除 +label `L` | ラベル設定 (`L`) +undo | Undo +max\_history | Undo回数を表示 +max\_history `A` | Undo回数を設定 (`A`: 0-) +crop | 表示範囲を選択範囲に限定 (無選択での場合は解除) +map\_alpha | 2Dマップの透明度を表示 (`A`) [\*1](#footnoteKey1) +map\_alpha `A` | 2Dマップの透明度を設定 (`A`: 0-1) +voxel\_grid | VoxelGridフィルタで点数を削減 +voxel\_grid `R` | VoxelGridフィルタで点数を削減 (voxelサイズ `R` \[メートル\]) +z\_range | 色をつけるZ座標の範囲を表示 [\*1](#footnoteKey1) +z\_range `Min` `Max` | 色をつけるZ座標の範囲を `Min` - `Max` \[メートル\]に設定 +perspective | 透視投影モード +ortho | 正投影モード +point\_size | 点の表示サイズを表示 [\*1](#footnoteKey1) +point\_size `Size` | 点の表示サイズを `Size` に設定 +segmentation\_param | セグメンテーション時の分離距離を表示 [\*1](#footnoteKey1) +segmentation\_param `D` `R` | セグメンテーション時の分離距離を `D` \[メートル\]、適用範囲を `R` \[メートル\]に設定 +fit\_inserting `AXIS`... | 貼り付け中の点群を既存の点群に位置合わせ [\*2](#footnoteKey2) (位置合わせを行う軸 `AXIS` をスペース区切りで複数指定 [\*3](#footnoteKey3)) +label\_segmentation\_param | ラベルを元にしてのセグメンテーション時の範囲と隣接する点群の最大距離を表示 [\*1](#footnoteKey1) +label\_segmentation\_param `D` `R` | ラベルを元にしてのセグメンテーション時の隣接する点群の最大距離を `D` \[メートル\]、範囲を `R` \[メートル\]に設定 +render\_label\_range `Min` `Max` | `Min` - `Max`の範囲内のラベルのみに色をつけて表示 +relabel `Min` `Max` `New` | `Min` - `Max`の範囲内のラベルを`New`値に設定 +unlabel `label1` `label2` `...` | `label1, label2, ...`以外のラベルを`0`に設定 +num\_fast\_render\_points | 操作中に表示する点の最大数を表示 +num\_fast\_render\_points `N` | 操作中に表示する点の最大数を `N` に設定
[1] 数値の表示
diff --git a/command.go b/command.go index 1112924..0a53da1 100644 --- a/command.go +++ b/command.go @@ -23,6 +23,7 @@ const ( defaultZMin = -5.0 defaultZMax = 5.0 defaultPointSize = 40.0 + defaultNumFastRenderPoints = 15000000 defaultSegmentationDistance = 0.08 defaultSegmentationRange = 5.0 @@ -86,7 +87,8 @@ type commandContext struct { zMin, zMax float32 projectionType ProjectionType - pointSize float32 + pointSize float32 + numFastRenderPoints int selectMode selectMode @@ -127,6 +129,7 @@ func (c *commandContext) Reset() { c.zMin = defaultZMin c.zMax = defaultZMax c.pointSize = defaultPointSize + c.numFastRenderPoints = defaultNumFastRenderPoints c.segmentationDistance = defaultSegmentationDistance c.segmentationRange = defaultSegmentationRange c.labelSegmentationRange = defaultLabelSegmentationRange @@ -177,6 +180,17 @@ func (c *commandContext) SetPointSize(ps float32) error { return nil } +func (c *commandContext) NumFastRenderPoints() int { + return c.numFastRenderPoints +} + +func (c *commandContext) SetNumFastRenderPoints(n int) error { + if n <= 100000 { + return errors.New("num fast render points must be >100000") + } + c.numFastRenderPoints = n + return nil +} func (c *commandContext) SegmentationParam() (float32, float32) { return c.segmentationDistance, c.segmentationRange } diff --git a/console.go b/console.go index 796cc20..d6162d4 100644 --- a/console.go +++ b/console.go @@ -194,6 +194,16 @@ var consoleCommands = map[string]func(c *console, updateSel updateSelectionFn, a return nil, errArgumentNumber } }, + "num_fast_render_points": func(c *console, updateSel updateSelectionFn, args []float32) ([][]float32, error) { + switch len(args) { + case 0: + return [][]float32{{float32(c.cmd.NumFastRenderPoints())}}, nil + case 1: + return nil, c.cmd.SetNumFastRenderPoints(int(args[0])) + default: + return nil, errArgumentNumber + } + }, "fov": func(c *console, updateSel updateSelectionFn, args []float32) ([][]float32, error) { switch len(args) { case 1: diff --git a/go.mod b/go.mod index d8b728b..dfc6bc9 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,11 @@ module github.com/seqsense/pcdeditor -go 1.16 +go 1.21 require ( github.com/seqsense/pcgol v0.0.0-20231024030105-2d5508f2110b github.com/seqsense/webgl-go v0.0.0-20231106035007-6fa7160d45ce gopkg.in/yaml.v3 v3.0.1 ) + +require github.com/zhuyie/golzf v0.0.0-20161112031142-8387b0307ade // indirect diff --git a/main_js.go b/main_js.go index 94f61ac..3c49b4c 100644 --- a/main_js.go +++ b/main_js.go @@ -15,7 +15,8 @@ import ( ) const ( - vib3DXAmp = 0.002 + vib3DXAmp = 0.002 + maxDrawArraysPoints = 30000000 // Firefox's limit ) var ( @@ -537,6 +538,7 @@ func (pe *pcdeditor) runImpl(ctx context.Context) error { pe.logPrint("WebGL context initialized") +L_MAIN: for { scale := float32(devicePixelRatioJS.Float()) scaled := func(v int) int { @@ -711,6 +713,18 @@ func (pe *pcdeditor) runImpl(ctx context.Context) error { pointSize := pe.cmd.PointSize() selectMode := pe.cmd.SelectMode() + samplingRatio := 1 + if pe.vi.dragging() { + totalPoints := 0 + if hasPointCloud && pp.Points > 0 { + totalPoints += pp.Points + } + if hasSubPointCloud && ppSub.Points > 0 && selectMode == selectModeInsert { + totalPoints += ppSub.Points + } + samplingRatio = 1 + totalPoints/pe.cmd.NumFastRenderPoints() + } + if hasPointCloud && pp.Points > 0 { // Render PointCloud gl.UseProgram(program) @@ -728,13 +742,14 @@ func (pe *pcdeditor) runImpl(ctx context.Context) error { gl.Uniform1ui(uMaxLabel, renderLabelMax) gl.BindBuffer(gl.ARRAY_BUFFER, posBuf) - gl.VertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, pp.Stride(), 0) - gl.VertexAttribIPointer(aVertexLabel, 1, gl.UNSIGNED_INT, pp.Stride(), 3*4) + sampledStride := pp.Stride() * samplingRatio + gl.VertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, sampledStride, 0) + gl.VertexAttribIPointer(aVertexLabel, 1, gl.UNSIGNED_INT, sampledStride, 3*4) gl.UniformMatrix4fv(uModelViewMatrixLocation, false, modelViewMatrix) gl.UniformMatrix4fv(uCropMatrixLocation, false, pe.cmd.CropMatrix()) gl.BindBuffer(gl.ARRAY_BUFFER, selectMaskBuf) - gl.VertexAttribIPointer(aSelectMask, 1, gl.UNSIGNED_INT, 4, 0) + gl.VertexAttribIPointer(aSelectMask, 1, gl.UNSIGNED_INT, 4*samplingRatio, 0) zMin, zMax := pe.cmd.ZRange() gl.Uniform1f(uZMinLocation, zMin) @@ -745,7 +760,10 @@ func (pe *pcdeditor) runImpl(ctx context.Context) error { gl.Uniform1f(uPointSizeBase, pointSize) - gl.DrawArrays(gl.POINTS, 0, pp.Points-1) + n := pp.Points / samplingRatio + for i := 0; i < n; i += maxDrawArraysPoints { + gl.DrawArrays(gl.POINTS, i, min(n-i, maxDrawArraysPoints)-1) + } clean() } @@ -758,14 +776,17 @@ func (pe *pcdeditor) runImpl(ctx context.Context) error { gl.UseProgram(programSub) clean := enableVertexAttribs(gl, aVertexPosition) gl.BindBuffer(gl.ARRAY_BUFFER, posSubBuf) - gl.VertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, ppSub.Stride(), 0) + gl.VertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, ppSub.Stride()*samplingRatio, 0) trans := cursorsToTrans(cursors) gl.UniformMatrix4fv( uModelViewMatrixLocationSub, false, modelViewMatrix.Mul(trans), ) gl.Uniform1f(uPointSizeBaseSub, pointSize) - gl.DrawArrays(gl.POINTS, 0, ppSub.Points-1) + n := ppSub.Points / samplingRatio + for i := 0; i < n; i += maxDrawArraysPoints { + gl.DrawArrays(gl.POINTS, i, min(n-i, maxDrawArraysPoints)-1) + } gl.Disable(gl.BLEND) clean() @@ -843,8 +864,11 @@ func (pe *pcdeditor) runImpl(ctx context.Context) error { gl.BindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, selectResultBuf) gl.Enable(gl.RASTERIZER_DISCARD) gl.BeginTransformFeedback(gl.POINTS) - gl.DrawArrays(gl.POINTS, 0, pp.Points-1) + for i, j := 0, 0; i < pp.Points; i, j = i+maxDrawArraysPoints, j+1 { + gl.DrawArrays(gl.POINTS, i, min(pp.Points-i, maxDrawArraysPoints)-1) + } gl.EndTransformFeedback() + gl.Disable(gl.RASTERIZER_DISCARD) gl.BindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, webgl.Buffer(js.Null())) @@ -898,382 +922,388 @@ func (pe *pcdeditor) runImpl(ctx context.Context) error { forceReload = false // Handle inputs - select { - case promise := <-pe.chImportPCD: - pe.logPrint("importing pcd") - if err := pe.cmd.ImportPCD(promise.data); err != nil { - promise.rejected(err) - break - } - pe.logPrint("pcd loaded") - promise.resolved("loaded") - case promise := <-pe.chImportSubPCD: - pe.logPrint("importing sub pcd") - if err := pe.cmd.ImportSubPCD(promise.data); err != nil { - promise.rejected(err) - break - } - pe.logPrint("sub pcd loaded") - promise.resolved("loaded") - case promise := <-pe.chImport2D: - pe.logPrint("loading 2D map") - data := promise.data.([2]js.Value) - if err := pe.cmd.Import2D(data[0], data[1]); err != nil { - promise.rejected(err) - break - } - pe.logPrint("2D map loaded") - promise.resolved("loaded") - case promise := <-pe.chExportPCD: - pe.logPrint("exporting pcd") - blob, err := pe.cmd.ExportPCD() - if err != nil { - promise.rejected(err) - break - } - pe.logPrint("pcd exported") - promise.resolved(blob) - case promise := <-pe.chExportSelectedPCD: - pe.logPrint("exporting selected points as pcd") - if !scanSelection(0, 0) { - promise.rejected(errors.New("failed to scan selected points")) - } - blob, err := pe.cmd.ExportSelectedPCD() - if err != nil { - promise.rejected(err) - break - } - pe.logPrint("pcd exported") - promise.resolved(blob) - case promise := <-pe.chReset: - pe.cmd.Reset() - promise.resolved("resetted") - case promise := <-pe.chCommand: - res, err := pe.cs.Run(promise.data.(string), func() error { - if scanSelection(0, 0) { - return nil + L_INPUT: + for { + select { + case promise := <-pe.chImportPCD: + pe.logPrint("importing pcd") + if err := pe.cmd.ImportPCD(promise.data); err != nil { + promise.rejected(err) + break } - return errors.New("failed to scan selected points") - }) - if err != nil { - promise.rejected(err) - break - } - promise.resolved(res) - case promise := <-pe.ch2D: - show2D = promise.data.(bool) - promise.resolved("changed") - case e := <-pe.chWheel: - var ok bool - e.DeltaY, ok = wheelNormalizer.Normalize(e.DeltaY) - if !ok { - break - } - switch { - case e.CtrlKey: - rate := 0.01 - if e.ShiftKey { - rate = 0.1 + pe.logPrint("pcd loaded") + promise.resolved("loaded") + case promise := <-pe.chImportSubPCD: + pe.logPrint("importing sub pcd") + if err := pe.cmd.ImportSubPCD(promise.data); err != nil { + promise.rejected(err) + break } - if len(pe.cmd.Cursors()) < 4 { - pe.cmd.SetSelectRange( - rangeTypeAuto, - pe.cmd.SelectRange(rangeTypeAuto)+float32(e.DeltaY*rate), - ) + pe.logPrint("sub pcd loaded") + promise.resolved("loaded") + case promise := <-pe.chImport2D: + pe.logPrint("loading 2D map") + data := promise.data.([2]js.Value) + if err := pe.cmd.Import2D(data[0], data[1]); err != nil { + promise.rejected(err) break } - r := 1.0 + float32(e.DeltaY*rate) - m, _ := pe.cmd.SelectMatrix() - pe.cmd.TransformCursors( - m.InvAffine(). - MulAffine(mat.Translate(0, 0, 0.5)). - MulAffine(mat.Scale(1, 1, r)). - MulAffine(mat.Translate(0, 0, -0.5)). - MulAffine(m), - ) - case e.ShiftKey: - rect, _ := pe.cmd.Rect() - if len(rect) > 0 { - var c mat.Vec3 - for _, p := range rect { - c = c.Add(p) + pe.logPrint("2D map loaded") + promise.resolved("loaded") + case promise := <-pe.chExportPCD: + pe.logPrint("exporting pcd") + blob, err := pe.cmd.ExportPCD() + if err != nil { + promise.rejected(err) + break + } + pe.logPrint("pcd exported") + promise.resolved(blob) + case promise := <-pe.chExportSelectedPCD: + pe.logPrint("exporting selected points as pcd") + if !scanSelection(0, 0) { + promise.rejected(errors.New("failed to scan selected points")) + } + blob, err := pe.cmd.ExportSelectedPCD() + if err != nil { + promise.rejected(err) + break + } + pe.logPrint("pcd exported") + promise.resolved(blob) + case promise := <-pe.chReset: + pe.cmd.Reset() + promise.resolved("resetted") + case promise := <-pe.chCommand: + res, err := pe.cs.Run(promise.data.(string), func() error { + if scanSelection(0, 0) { + return nil } - c = c.Mul(1.0 / float32(len(rect))) - r := 1.0 + float32(e.DeltaY)*0.01 - pe.cmd.TransformCursors( - mat.Translate(c[0], c[1], c[2]). - MulAffine(mat.Scale(r, r, r)). - MulAffine(mat.Translate(-c[0], -c[1], -c[2])), - ) + return errors.New("failed to scan selected points") + }) + if err != nil { + promise.rejected(err) + break } - default: - pe.vi.wheel(&e) - } - case e := <-pe.chMouseDown: - gl.Canvas.Focus() - if e.Button == 0 { - pe.cg.DragStart() - if p, ok := cursorOnSelect(e); ok { - pe.cmd.PushCursors() - moveStart = selectPointOrtho( - &modelViewMatrix, &projectionMatrix, - scaled(e.OffsetX), scaled(e.OffsetY), width, height, p, - ) - continue + promise.resolved(res) + case promise := <-pe.ch2D: + show2D = promise.data.(bool) + promise.resolved("changed") + case e := <-pe.chWheel: + var ok bool + e.DeltaY, ok = wheelNormalizer.Normalize(e.DeltaY) + if !ok { + break } - } - moveStart = nil - pe.vi.mouseDragStart(&e) - case e := <-pe.chMouseUp: - if e.Button == 0 { - pe.cg.DragEnd() - } - if moveStart != nil { - pe.cmd.PopCursors() - moveEnd := selectPointOrtho( - &modelViewMatrix, &projectionMatrix, - scaled(e.OffsetX), scaled(e.OffsetY), width, height, moveStart, - ) - var trans mat.Mat4 switch { + case e.CtrlKey: + rate := 0.01 + if e.ShiftKey { + rate = 0.1 + } + if len(pe.cmd.Cursors()) < 4 { + pe.cmd.SetSelectRange( + rangeTypeAuto, + pe.cmd.SelectRange(rangeTypeAuto)+float32(e.DeltaY*rate), + ) + break + } + r := 1.0 + float32(e.DeltaY*rate) + m, _ := pe.cmd.SelectMatrix() + pe.cmd.TransformCursors( + m.InvAffine(). + MulAffine(mat.Translate(0, 0, 0.5)). + MulAffine(mat.Scale(1, 1, r)). + MulAffine(mat.Translate(0, 0, -0.5)). + MulAffine(m), + ) case e.ShiftKey: rect, _ := pe.cmd.Rect() - trans = dragRotation(*moveStart, *moveEnd, rect, &modelViewMatrix) + if len(rect) > 0 { + var c mat.Vec3 + for _, p := range rect { + c = c.Add(p) + } + c = c.Mul(1.0 / float32(len(rect))) + r := 1.0 + float32(e.DeltaY)*0.01 + pe.cmd.TransformCursors( + mat.Translate(c[0], c[1], c[2]). + MulAffine(mat.Scale(r, r, r)). + MulAffine(mat.Translate(-c[0], -c[1], -c[2])), + ) + } default: - trans = dragTranslation(*moveStart, *moveEnd) + pe.vi.wheel(&e) + } + case e := <-pe.chMouseDown: + gl.Canvas.Focus() + if e.Button == 0 { + pe.cg.DragStart() + if p, ok := cursorOnSelect(e); ok { + pe.cmd.PushCursors() + moveStart = selectPointOrtho( + &modelViewMatrix, &projectionMatrix, + scaled(e.OffsetX), scaled(e.OffsetY), width, height, p, + ) + continue L_MAIN + } } - pe.cmd.TransformCursors(trans) moveStart = nil - continue - } - pe.vi.mouseDragEnd(&e) - case e := <-pe.chMouseDrag: - pe.cg.Move() - if e.Button == 0 && moveStart != nil { - pe.cmd.PopCursors() - pe.cmd.PushCursors() - - if e.ShiftKey { - pe.SetCursor(cursorGrabbing) - } else { - pe.SetCursor(cursorMove) + pe.vi.mouseDragStart(&e) + case e := <-pe.chMouseUp: + if e.Button == 0 { + pe.cg.DragEnd() } - - moveEnd := selectPointOrtho( - &modelViewMatrix, &projectionMatrix, - scaled(e.OffsetX), scaled(e.OffsetY), width, height, moveStart, - ) - var trans mat.Mat4 - switch { - case e.ShiftKey: - rect, _ := pe.cmd.Rect() - trans = dragRotation(*moveStart, *moveEnd, rect, &modelViewMatrix) - default: - trans = dragTranslation(*moveStart, *moveEnd) + if moveStart != nil { + pe.cmd.PopCursors() + moveEnd := selectPointOrtho( + &modelViewMatrix, &projectionMatrix, + scaled(e.OffsetX), scaled(e.OffsetY), width, height, moveStart, + ) + var trans mat.Mat4 + switch { + case e.ShiftKey: + rect, _ := pe.cmd.Rect() + trans = dragRotation(*moveStart, *moveEnd, rect, &modelViewMatrix) + default: + trans = dragTranslation(*moveStart, *moveEnd) + } + pe.cmd.TransformCursors(trans) + moveStart = nil + continue L_MAIN } - pe.cmd.TransformCursors(trans) - continue - } - pe.vi.mouseDrag(&e) - case e := <-pe.chMouseMove: - if _, ok := cursorOnSelect(e); ok { - if e.ShiftKey { - pe.SetCursor(cursorGrab) - } else { - pe.SetCursor(cursorMove) + pe.vi.mouseDragEnd(&e) + case e := <-pe.chMouseDrag: + pe.cg.Move() + if e.Button == 0 && moveStart != nil { + pe.cmd.PopCursors() + pe.cmd.PushCursors() + + if e.ShiftKey { + pe.SetCursor(cursorGrabbing) + } else { + pe.SetCursor(cursorMove) + } + + moveEnd := selectPointOrtho( + &modelViewMatrix, &projectionMatrix, + scaled(e.OffsetX), scaled(e.OffsetY), width, height, moveStart, + ) + var trans mat.Mat4 + switch { + case e.ShiftKey: + rect, _ := pe.cmd.Rect() + trans = dragRotation(*moveStart, *moveEnd, rect, &modelViewMatrix) + default: + trans = dragTranslation(*moveStart, *moveEnd) + } + pe.cmd.TransformCursors(trans) + continue L_MAIN } - } else { - pe.SetCursor(cursorAuto) - } - case e := <-pe.chClick: - gl.Canvas.Focus() - if e.Button != 0 || !pe.cg.Click() { - continue - } - ok := scanSelection(scaled(e.OffsetX), scaled(e.OffsetY)) - if !ok { - updateSelectMask() - continue - } - var p *mat.Vec3 - switch projectionType { - case ProjectionPerspective: - p, ok = selectPoint( - pp, pe.cmd.SelectMask(), projectionType, &modelViewMatrix, &projectionMatrix, - scaled(e.OffsetX), scaled(e.OffsetY), width, height, pointSelectRange, - ) - case ProjectionOrthographic: - p = selectPointOrtho( - &modelViewMatrix, &projectionMatrix, scaled(e.OffsetX), scaled(e.OffsetY), width, height, nil, - ) - default: - ok = false - } - if !ok { - updateSelectMask() - continue - } - switch { - case e.ShiftKey: - if len(pe.cmd.Cursors()) < 3 { - pe.cmd.SetCursor(1, *p) + pe.vi.mouseDrag(&e) + case e := <-pe.chMouseMove: + if _, ok := cursorOnSelect(e); ok { + if e.ShiftKey { + pe.SetCursor(cursorGrab) + } else { + pe.SetCursor(cursorMove) + } } else { - pe.cmd.SetCursor(3, *p) + pe.SetCursor(cursorAuto) } - case e.AltKey: - if projectionType != ProjectionPerspective { - break + continue L_INPUT // process input again without rendering + case e := <-pe.chClick: + gl.Canvas.Focus() + if e.Button != 0 || !pe.cg.Click() { + continue L_MAIN } - if ok := scanSelection(scaled(e.OffsetX), scaled(e.OffsetY)); ok { - pe.cmd.SelectSegment(*p) + ok := scanSelection(scaled(e.OffsetX), scaled(e.OffsetY)) + if !ok { updateSelectMask() + continue L_MAIN } - case e.CtrlKey: - if projectionType != ProjectionPerspective { - break + var p *mat.Vec3 + switch projectionType { + case ProjectionPerspective: + p, ok = selectPoint( + pp, pe.cmd.SelectMask(), projectionType, &modelViewMatrix, &projectionMatrix, + scaled(e.OffsetX), scaled(e.OffsetY), width, height, pointSelectRange, + ) + case ProjectionOrthographic: + p = selectPointOrtho( + &modelViewMatrix, &projectionMatrix, scaled(e.OffsetX), scaled(e.OffsetY), width, height, nil, + ) + default: + ok = false } - if ok := scanSelection(scaled(e.OffsetX), scaled(e.OffsetY)); ok { - err := pe.cmd.SelectLabelSegment(*p) - if err != nil { - pe.logPrint("Selection by label failed: " + err.Error()) + if !ok { + updateSelectMask() + continue L_MAIN + } + switch { + case e.ShiftKey: + if len(pe.cmd.Cursors()) < 3 { + pe.cmd.SetCursor(1, *p) } else { + pe.cmd.SetCursor(3, *p) + } + case e.AltKey: + if projectionType != ProjectionPerspective { + break + } + if ok := scanSelection(scaled(e.OffsetX), scaled(e.OffsetY)); ok { + pe.cmd.SelectSegment(*p) updateSelectMask() } + case e.CtrlKey: + if projectionType != ProjectionPerspective { + break + } + if ok := scanSelection(scaled(e.OffsetX), scaled(e.OffsetY)); ok { + err := pe.cmd.SelectLabelSegment(*p) + if err != nil { + pe.logPrint("Selection by label failed: " + err.Error()) + } else { + updateSelectMask() + } + } + default: + if len(pe.cmd.Cursors()) < 2 { + pe.cmd.SetCursor(0, *p) + } else { + pe.cmd.SetCursor(2, *p) + } } - default: - if len(pe.cmd.Cursors()) < 2 { - pe.cmd.SetCursor(0, *p) - } else { - pe.cmd.SetCursor(2, *p) - } - } - case e := <-pe.chKey: - switch e.Code { - case "Escape": - if moveStart != nil { - pe.cmd.PopCursors() - moveStart = nil - } else { - pe.cmd.UnsetCursors() - } - case "Enter": - if err := pe.cmd.FinalizeCurrentMode(); err != nil { - pe.logPrint("Failed: " + err.Error()) - } - case "Delete", "Backspace", "Digit0", "Digit1": + case e := <-pe.chKey: switch e.Code { - case "Delete", "Backspace": - if ok := scanSelection(0, 0); ok { - pe.cmd.Delete() - if !e.ShiftKey && !e.CtrlKey { - pe.cmd.UnsetCursors() - } + case "Escape": + if moveStart != nil { + pe.cmd.PopCursors() + moveStart = nil + } else { + pe.cmd.UnsetCursors() } - case "Digit0", "Digit1": - var l uint32 - if e.Code == "Digit1" { - l = 1 + case "Enter": + if err := pe.cmd.FinalizeCurrentMode(); err != nil { + pe.logPrint("Failed: " + err.Error()) } - if ok := scanSelection(0, 0); ok { - pe.cmd.Label(l) + case "Delete", "Backspace", "Digit0", "Digit1": + switch e.Code { + case "Delete", "Backspace": + if ok := scanSelection(0, 0); ok { + pe.cmd.Delete() + if !e.ShiftKey && !e.CtrlKey { + pe.cmd.UnsetCursors() + } + } + case "Digit0", "Digit1": + var l uint32 + if e.Code == "Digit1" { + l = 1 + } + if ok := scanSelection(0, 0); ok { + pe.cmd.Label(l) + } } - } - case "KeyZ": - if e.CtrlKey { + case "KeyZ": + if e.CtrlKey { + pe.cmd.Undo() + } + case "KeyU": pe.cmd.Undo() + case "KeyF": + pe.cmd.AddSurface(defaultResolution) + case "KeyV", "KeyH": + switch e.Code { + case "KeyV": + pe.cmd.SnapVertical() + case "KeyH": + pe.cmd.SnapHorizontal() + } + case "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "PageUp", "PageDown": + var dx, dy, dz float32 + switch e.Code { + case "ArrowUp": + dy = 0.05 + case "ArrowDown": + dy = -0.05 + case "ArrowLeft": + dx = -0.05 + case "ArrowRight": + dx = 0.05 + case "PageUp": + dz = 0.05 + case "PageDown": + dz = -0.05 + } + s, c := math.Sincos(pe.vi.yaw) + pe.cmd.TransformCursors(mat.Translate( + float32(c)*dx-float32(s)*dy, + float32(s)*dx+float32(c)*dy, + dz, + )) + case "Home", "End": + var dyaw float32 + switch e.Code { + case "Home": + dyaw = 0.005 + case "End": + dyaw = -0.005 + } + center := pe.cmd.RectCenterPos() + pe.cmd.TransformCursors( + mat.Translate(center[0], center[1], center[2]). + Mul(mat.Rotate(0, 0, 1, dyaw)). + Mul(mat.Translate(-center[0], -center[1], -center[2])), + ) + case "KeyW", "KeyA", "KeyS", "KeyD", "KeyQ", "KeyE": + switch e.Code { + case "KeyW": + pe.vi.Move(0.05, 0, 0) + case "KeyA": + pe.vi.Move(0, 0.05, 0) + case "KeyS": + pe.vi.Move(-0.05, 0, 0) + case "KeyD": + pe.vi.Move(0, -0.05, 0) + case "KeyQ": + pe.vi.Move(0, 0, 0.02) + case "KeyE": + pe.vi.Move(0, 0, -0.02) + } + case "BracketRight", "Backslash": + switch e.Code { + case "BracketRight": + pe.vi.IncreaseFOV() + case "Backslash": + pe.vi.DecreaseFOV() + } + case "F10": + pe.cmd.Crop() + case "F11": + pe.vi.SnapYaw() + case "F12": + pe.vi.SnapPitch() + case "KeyP": + vib3D = !vib3D } - case "KeyU": - pe.cmd.Undo() - case "KeyF": - pe.cmd.AddSurface(defaultResolution) - case "KeyV", "KeyH": - switch e.Code { - case "KeyV": - pe.cmd.SnapVertical() - case "KeyH": - pe.cmd.SnapHorizontal() - } - case "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "PageUp", "PageDown": - var dx, dy, dz float32 - switch e.Code { - case "ArrowUp": - dy = 0.05 - case "ArrowDown": - dy = -0.05 - case "ArrowLeft": - dx = -0.05 - case "ArrowRight": - dx = 0.05 - case "PageUp": - dz = 0.05 - case "PageDown": - dz = -0.05 - } - s, c := math.Sincos(pe.vi.yaw) - pe.cmd.TransformCursors(mat.Translate( - float32(c)*dx-float32(s)*dy, - float32(s)*dx+float32(c)*dy, - dz, - )) - case "Home", "End": - var dyaw float32 - switch e.Code { - case "Home": - dyaw = 0.005 - case "End": - dyaw = -0.005 - } - center := pe.cmd.RectCenterPos() - pe.cmd.TransformCursors( - mat.Translate(center[0], center[1], center[2]). - Mul(mat.Rotate(0, 0, 1, dyaw)). - Mul(mat.Translate(-center[0], -center[1], -center[2])), - ) - case "KeyW", "KeyA", "KeyS", "KeyD", "KeyQ", "KeyE": - switch e.Code { - case "KeyW": - pe.vi.Move(0.05, 0, 0) - case "KeyA": - pe.vi.Move(0, 0.05, 0) - case "KeyS": - pe.vi.Move(-0.05, 0, 0) - case "KeyD": - pe.vi.Move(0, -0.05, 0) - case "KeyQ": - pe.vi.Move(0, 0, 0.02) - case "KeyE": - pe.vi.Move(0, 0, -0.02) - } - case "BracketRight", "Backslash": - switch e.Code { - case "BracketRight": - pe.vi.IncreaseFOV() - case "Backslash": - pe.vi.DecreaseFOV() - } - case "F10": - pe.cmd.Crop() - case "F11": - pe.vi.SnapYaw() - case "F12": - pe.vi.SnapPitch() - case "KeyP": - vib3D = !vib3D - } - case <-pe.chContextLost: - return errContextLostEvent - case <-tick.C: - if vib3D { - if vib3DX < 0 { - vib3DX = vib3DXAmp + case <-pe.chContextLost: + return errContextLostEvent + case <-tick.C: + if vib3D { + if vib3DX < 0 { + vib3DX = vib3DXAmp + } else { + vib3DX = -vib3DXAmp + } } else { - vib3DX = -vib3DXAmp + vib3DX = 0 + continue L_INPUT // process input again without rendering } - } else { - vib3DX = 0 + case <-ctx.Done(): + return nil } - case <-ctx.Done(): - return nil + break } } } diff --git a/view_js.go b/view_js.go index d74c7da..44647ac 100644 --- a/view_js.go +++ b/view_js.go @@ -96,6 +96,10 @@ func (v *viewImpl) wheel(e *webgl.WheelEvent) { } } +func (v *viewImpl) dragging() bool { + return v.drag0 != nil +} + func (v *viewImpl) mouseDragStart(e *webgl.MouseEvent) { v.drag0 = e v.yaw0 = v.yaw