pointcloud

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 12, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

README

Point Cloud Viewer

A simple point cloud viewer built with Fyne. This repository primarily offers a Fyne widget for viewing point clouds, but it also contains a standalone viewer. The standalone viewer

Reads PLY, PCD, PTS, and XYZ files and renders them as interactive 3D point clouds with mouse-controlled rotation and zoom.

Go Reference

Gopher

Examples

Viewing 970k point goat scull from from Artec 3D.

Screenshot

Simulated seabed from _examples/seabed.

Screenshot

Features

  • Supports PLY, PCD, PTS, and XYZ point cloud formats
  • Interactive arcball rotation, panning (Shift+drag), and scroll-wheel zoom
  • Orientation cube with click-to-snap face selection
  • Home view and zoom-to-fit buttons
  • Point picking with coordinate and RGB display
  • Scale bar with configurable units
  • FPS counter overlay
  • Level-of-detail (LOD) decimation during interaction
  • Y-up and Z-up coordinate system support
  • Keyboard shortcuts: h (home), f (fit), +/- (zoom), arrow keys (rotate)

Installation

Library

To use the viewer widget in your own Fyne application:

go get github.com/borud/pointcloud
Demo application

Pre-built binaries for Linux (amd64, arm64), macOS (arm64), and Windows (amd64) are available on the releases page.

Or build from source:

go install github.com/borud/pointcloud/cmd/pointcloud@latest

Using the widget

The Viewer is a standard Fyne widget that you can embed in any Fyne application. It uses a functional options pattern for configuration.

Minimal example
package main

import (
    "log"

    "fyne.io/fyne/v2/app"
    "github.com/borud/pointcloud"
)

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Point Cloud")

    v := pointcloud.New()

    pc, err := pointcloud.ReadFile("model.ply")
    if err != nil {
        log.Fatal(err)
    }
    pc.Normalize()
    v.SetScale(pc.NormScale)
    v.SetPoints(pc.Points)

    w.SetContent(v)
    w.ShowAndRun()
}
Configuring the viewer with options

Use functional options to customize appearance when creating the viewer.

v := pointcloud.New(
    pointcloud.WithBackgroundColor(color.RGBA{30, 30, 30, 255}),
    pointcloud.WithDefaultPointColor(color.RGBA{255, 150, 255, 255}),
    pointcloud.WithOrientationCube(true),
    pointcloud.WithHomeButton(true),
    pointcloud.WithZoomFitButton(true),
    pointcloud.WithInfoLabel(true),
    pointcloud.WithScaleBar(true),
    pointcloud.WithScaleUnit("m"),
    pointcloud.WithFPS(true),
    pointcloud.WithMaxZoomOutFraction(0.3),
)
v.SetUpAxis(pointcloud.ZUp)
Loading from an io.Reader

Each format has a dedicated reader function that accepts an io.Reader, and there is a ReadFile convenience function that auto-detects the format from the file extension.

// Auto-detect format from file extension.
pc, err := pointcloud.ReadFile("scan.pcd")

// Or read a specific format from any io.Reader.
f, _ := os.Open("scan.ply")
defer f.Close()
pc, err := pointcloud.ReadPLY(f)

Available readers: ReadPLY, ReadPCD, ReadPTS, ReadXYZ.

Working with point data directly

You can construct a PointCloud programmatically instead of reading from a file.

pc := &pointcloud.PointCloud{
    Points: []pointcloud.Point3D{
        {X: 0, Y: 0, Z: 0, R: 255, G: 0, B: 0, HasColor: true},
        {X: 1, Y: 0, Z: 0, R: 0, G: 255, B: 0, HasColor: true},
        {X: 0, Y: 1, Z: 0, R: 0, G: 0, B: 255, HasColor: true},
        {X: 0, Y: 0, Z: 1, R: 255, G: 255, B: 0, HasColor: true},
    },
}
pc.ComputeBounds()
pc.Normalize()

v := pointcloud.New()
v.SetScale(pc.NormScale)
v.SetPoints(pc.Points)
Updating points without resetting the view

Use SetPointsPreserveView to replace the displayed points while keeping the current orientation, zoom, and pan. This is useful for streaming or live-updating point clouds.

// Initial load.
v.SetPoints(pc.Points)

// Later, update with new data without resetting the camera.
v.SetPointsPreserveView(newPoints)
Controlling orientation programmatically
// Set a custom home orientation using a quaternion.
q := pointcloud.QuatFromAxisAngle(0, 1, 0, math.Pi/2)  // 90 degrees around Y
v.SetOrientation(q)

// Snap back to default home view.
v.HomeView()

// Auto-fit the point cloud to the viewport.
v.ZoomToExtents()
Writing point clouds
f, _ := os.Create("output.ply")
defer f.Close()
pointcloud.WritePLY(f, pc)

Controls

The viewer has two camera modes: orbit (default) and flythrough (first-person). Toggle between them with the g key or the eye button in the toolbar.

Orbit mode
Input Action
Drag Arcball rotation
Shift+Drag Pan
Scroll wheel Zoom in/out
Click Pick nearest point (shows coordinates in info label)
h Reset to home view
f Zoom to fit all points
+ / - Zoom in / out
Arrow keys Rotate in 5-degree steps
g Enter flythrough mode
Flythrough mode

In flythrough mode the camera moves freely through the point cloud using FPS-style controls.

Input Action
W / S or Up / Down Move forward / backward
A / D or Left / Right Strafe left / right
Space Move up (world space)
Q Move down (world space)
Drag Mouse look
Scroll wheel Adjust movement speed
g Return to orbit mode
Escape Return to orbit mode
h Return to orbit mode and reset home view
Orientation cube

Click a face, edge midpoint, or corner of the orientation cube to snap the view to that direction.

Benchmarking

Install benchstat for comparing results:

go install golang.org/x/perf/cmd/benchstat@latest

Run benchmarks on your current (known-good) code and save as baseline:

make bench
mv bench/new.txt bench/old.txt

After making changes, run benchmarks again and compare:

make bench
make benchstat

benchstat will show per-benchmark deltas with p-values, making it easy to spot regressions:

               │  old.txt   │              new.txt               │
               │   sec/op   │   sec/op     vs base               │
Draw_1M-10       45.2ms ± 1%   44.8ms ± 2%  ~ (p=0.394 n=6)
Projection_1M    12.3ms ± 0%   15.1ms ± 1%  +22.76% (p=0.002 n=6)

Sample data

The data/ directory contains sample PLY files from Artec 3D. These files are provided by Artec Group 3D Scanning Solutions and remain the property of their respective owners.

Documentation

Overview

Package pointcloud provides point cloud data types and format readers.

Index

Constants

This section is empty.

Variables

View Source
var HomeOrientation = QuatFromEulerXY(-0.3, -math.Pi/4)

HomeOrientation is the default home view: side-on, slightly elevated.

Functions

func SupportedExtensions

func SupportedExtensions() []string

SupportedExtensions returns all file extensions the readers handle.

func WritePLY

func WritePLY(w io.Writer, pc *PointCloud) error

WritePLY writes a point cloud in PLY ASCII format.

Types

type CubeColors

type CubeColors struct {
	Faces      [6]color.RGBA // Z+, Z-, X+, X-, Y+, Y-
	EdgeColor  color.RGBA
	LabelColor color.RGBA
	AxisColors [3]color.RGBA // X, Y, Z
}

CubeColors configures the colors of the orientation cube.

func DefaultCubeColors

func DefaultCubeColors() CubeColors

DefaultCubeColors returns the default orientation cube colors.

type Option

type Option func(*config)

Option configures a Viewer during construction.

func WithBackgroundColor

func WithBackgroundColor(c color.RGBA) Option

WithBackgroundColor sets the canvas background color.

func WithCubeColors

func WithCubeColors(cc CubeColors) Option

WithCubeColors sets the orientation cube colors.

func WithDefaultPointColor

func WithDefaultPointColor(c color.RGBA) Option

WithDefaultPointColor sets the fallback color for points without RGB data.

func WithFPS

func WithFPS(show bool) Option

WithFPS controls whether the FPS counter is displayed.

func WithFPSColor

func WithFPSColor(c color.RGBA) Option

WithFPSColor sets the FPS counter text color.

func WithFPSSize

func WithFPSSize(size float32) Option

WithFPSSize sets the FPS counter font size. The default is 14.

func WithFPSStyle

func WithFPSStyle(s fyne.TextStyle) Option

WithFPSStyle sets the FPS counter text style.

func WithFlythroughButton

func WithFlythroughButton(show bool) Option

WithFlythroughButton controls whether the flythrough toggle button is displayed.

func WithFlythroughEnabled

func WithFlythroughEnabled(on bool) Option

WithFlythroughEnabled sets whether flythrough mode is initially active.

func WithHomeButton

func WithHomeButton(show bool) Option

WithHomeButton controls whether the home button is displayed.

func WithHomeOrientation

func WithHomeOrientation(q Quat) Option

WithHomeOrientation sets the default home view orientation.

func WithInfoLabel

func WithInfoLabel(show bool) Option

WithInfoLabel controls whether the point info label is displayed.

func WithInfoLabelColor

func WithInfoLabelColor(c color.RGBA) Option

WithInfoLabelColor sets the info label text color.

func WithInfoLabelSize

func WithInfoLabelSize(size float32) Option

WithInfoLabelSize sets the info label font size. The default is 12.

func WithInfoLabelStyle

func WithInfoLabelStyle(s fyne.TextStyle) Option

WithInfoLabelStyle sets the info label text style (font).

func WithInitialZoom

func WithInitialZoom(z float64) Option

WithInitialZoom sets the starting zoom level.

func WithMaxZoomOutFraction

func WithMaxZoomOutFraction(f float64) Option

WithMaxZoomOutFraction sets the minimum visible fraction of the largest canvas dimension that the point cloud must occupy. For example, 0.2 means the user cannot zoom out past the point where the cloud covers less than 20% of the viewport. The default is 0.2.

func WithOrientationCube

func WithOrientationCube(show bool) Option

WithOrientationCube controls whether the orientation cube is displayed.

func WithScaleBar

func WithScaleBar(show bool) Option

WithScaleBar controls whether the scale bar is displayed.

func WithScaleBarColor

func WithScaleBarColor(c color.RGBA) Option

WithScaleBarColor sets the scale bar color.

func WithScaleUnit

func WithScaleUnit(unit string) Option

WithScaleUnit sets the unit label for the scale bar (e.g. "m").

func WithScaleUnitScale

func WithScaleUnitScale(s float64) Option

WithScaleUnitScale sets the unit multiplier for the scale bar. For example, if data is in meters and you want to display millimeters, set this to 1000.

func WithZoomFitButton

func WithZoomFitButton(show bool) Option

WithZoomFitButton controls whether the zoom-fit button is displayed.

type Point3D

type Point3D struct {
	X, Y, Z  float64
	R, G, B  uint8
	HasColor bool
}

Point3D represents a point in 3D space with optional color.

type PointCloud

type PointCloud struct {
	Points           []Point3D
	MinX, MinY, MinZ float64
	MaxX, MaxY, MaxZ float64

	// NormScale is the scale factor applied by Normalize (2.0/maxDim).
	// Zero if Normalize has not been called.
	NormScale float64
	// NormCenter is the center point subtracted during Normalize.
	NormCenter [3]float64
	// contains filtered or unexported fields
}

PointCloud holds a collection of 3D points with bounding box metadata.

func ReadFile

func ReadFile(path string) (*PointCloud, error)

ReadFile detects the format by file extension and reads the point cloud.

func ReadPCD

func ReadPCD(r io.Reader) (*PointCloud, error)

ReadPCD reads a point cloud from a PCD (Point Cloud Library) format file. Supports ASCII and binary formats.

func ReadPLY

func ReadPLY(r io.Reader) (*PointCloud, error)

ReadPLY reads a point cloud from a PLY (Stanford Polygon Format) file. Supports ASCII, binary_little_endian, and binary_big_endian formats.

func ReadPTS

func ReadPTS(r io.Reader) (*PointCloud, error)

ReadPTS reads a point cloud from a PTS (Leica) format file. The first line is the point count. Subsequent lines contain: x y z intensity [r g b] (space-separated).

func ReadXYZ

func ReadXYZ(r io.Reader) (*PointCloud, error)

ReadXYZ reads a point cloud from an XYZ format file. Each line should contain at least 3 whitespace or comma-separated floats (x, y, z). Lines starting with '#' or '//' are treated as comments. Extra columns beyond x, y, z are ignored.

func (*PointCloud) ComputeBounds

func (pc *PointCloud) ComputeBounds()

ComputeBounds calculates the bounding box of the point cloud.

func (*PointCloud) Normalize

func (pc *PointCloud) Normalize()

Normalize centers the point cloud at the origin and scales it so the largest dimension spans [-1, 1].

type Quat

type Quat struct {
	X, Y, Z, W float64
}

Quat represents a unit quaternion for 3D rotation.

func QuatFromAxisAngle

func QuatFromAxisAngle(ax, ay, az, angle float64) Quat

QuatFromAxisAngle creates a quaternion from an axis and angle (radians).

func QuatFromEulerXY

func QuatFromEulerXY(ax, ay float64) Quat

QuatFromEulerXY creates a quaternion matching the old Euler convention: rotate by ay around Y, then by ax around X.

func QuatIdentity

func QuatIdentity() Quat

QuatIdentity returns the identity quaternion (no rotation).

func (Quat) Conjugate

func (q Quat) Conjugate() Quat

Conjugate returns the conjugate of the quaternion, which for unit quaternions is also the inverse rotation.

func (Quat) Mul

func (q Quat) Mul(b Quat) Quat

Mul returns the Hamilton product q*b (apply b first, then q).

func (Quat) Normalize

func (q Quat) Normalize() Quat

Normalize returns the quaternion scaled to unit length.

func (Quat) RotateVec3

func (q Quat) RotateVec3(v [3]float64) [3]float64

RotateVec3 rotates a 3D vector by the quaternion using the formula q*v*q^-1.

func (Quat) ToMatrix

func (q Quat) ToMatrix() [9]float64

ToMatrix returns a row-major 3x3 rotation matrix.

type UpAxis

type UpAxis int

UpAxis selects which input data axis maps to screen-up.

const (
	// YUp treats the Y axis as up (common in computer graphics).
	YUp UpAxis = iota
	// ZUp treats the Z axis as up (common in surveying and LiDAR).
	ZUp
)

type Viewer

type Viewer struct {
	widget.BaseWidget

	// OnFlythroughChanged is called when flythrough mode is toggled,
	// either via the UI button, the 'G' key, or SetFlythrough.
	OnFlythroughChanged func(on bool)
	// contains filtered or unexported fields
}

Viewer is a self-contained point cloud viewer widget for Fyne applications.

It provides:

  • Interactive 3D rendering with perspective projection
  • Arcball rotation via mouse drag
  • Panning via Shift+drag
  • Zoom via scroll wheel
  • An orientation cube for quick view snapping
  • Home and zoom-to-fit buttons

func New

func New(opts ...Option) *Viewer

New creates a new point cloud viewer widget with default settings. The default up-axis is YUp. Use Viewer.SetUpAxis to change it. Functional options can be provided to configure the viewer at creation time.

func (*Viewer) CreateRenderer

func (v *Viewer) CreateRenderer() fyne.WidgetRenderer

CreateRenderer implements fyne.Widget.

func (*Viewer) GetUpAxis

func (v *Viewer) GetUpAxis() UpAxis

GetUpAxis returns the current up-axis setting.

func (*Viewer) HomeView

func (v *Viewer) HomeView()

HomeView resets the orientation, zoom, and pan to the default home view.

func (*Viewer) IsFlythrough

func (v *Viewer) IsFlythrough() bool

IsFlythrough returns true if flythrough mode is currently active.

func (*Viewer) LODEnabled

func (v *Viewer) LODEnabled() bool

LODEnabled returns whether LOD decimation is currently enabled.

func (*Viewer) LODTargetSize

func (v *Viewer) LODTargetSize() int

LODTargetSize returns the current LOD target point count.

func (*Viewer) MaxZoomOutFraction

func (v *Viewer) MaxZoomOutFraction() float64

MaxZoomOutFraction returns the current zoom-out fraction limit.

func (*Viewer) Orientation

func (v *Viewer) Orientation() Quat

Orientation returns the current view orientation as a quaternion.

func (*Viewer) Pan

func (v *Viewer) Pan() (x, y float64)

Pan returns the current pan offset in Fyne DIP units.

func (*Viewer) SetBackgroundColor

func (v *Viewer) SetBackgroundColor(c color.RGBA)

SetBackgroundColor sets the canvas background color. The default is black.

func (*Viewer) SetCubeColors

func (v *Viewer) SetCubeColors(cc CubeColors)

SetCubeColors updates the orientation cube colors at runtime. Has no effect if the cube was hidden via WithOrientationCube(false).

func (*Viewer) SetDefaultPointColor

func (v *Viewer) SetDefaultPointColor(c color.RGBA)

SetDefaultPointColor sets the fallback color for points without RGB data.

func (*Viewer) SetFPSColor

func (v *Viewer) SetFPSColor(c color.RGBA)

SetFPSColor sets the FPS counter text color.

func (*Viewer) SetFPSEnabled

func (v *Viewer) SetFPSEnabled(on bool)

SetFPSEnabled shows or hides the FPS counter. The viewer must have been created with WithFPS(true) for this to have any effect.

func (*Viewer) SetFPSSize

func (v *Viewer) SetFPSSize(size float32)

SetFPSSize sets the FPS counter font size.

func (*Viewer) SetFPSStyle

func (v *Viewer) SetFPSStyle(s fyne.TextStyle)

SetFPSStyle sets the FPS counter text style.

func (*Viewer) SetFlythrough

func (v *Viewer) SetFlythrough(on bool)

SetFlythrough enables or disables flythrough (first-person) camera mode.

func (*Viewer) SetInfoLabelColor

func (v *Viewer) SetInfoLabelColor(c color.RGBA)

SetInfoLabelColor sets the info label text color at runtime. Has no effect if the info label was hidden via WithInfoLabel(false).

func (*Viewer) SetInfoLabelSize

func (v *Viewer) SetInfoLabelSize(size float32)

SetInfoLabelSize sets the info label font size at runtime. Has no effect if the info label was hidden via WithInfoLabel(false).

func (*Viewer) SetInfoLabelStyle

func (v *Viewer) SetInfoLabelStyle(s fyne.TextStyle)

SetInfoLabelStyle sets the info label text style at runtime. Has no effect if the info label was hidden via WithInfoLabel(false).

func (*Viewer) SetLODEnabled

func (v *Viewer) SetLODEnabled(enabled bool)

SetLODEnabled enables or disables LOD (level-of-detail) decimation during mouse interaction. When enabled, a decimated point set is rendered while dragging or scrolling for responsive frame rates. The full cloud is restored after a short idle period. LOD is enabled by default.

func (*Viewer) SetLODTargetSize

func (v *Viewer) SetLODTargetSize(n int)

SetLODTargetSize sets the target number of points in the decimated LOD set. Smaller values give faster interaction at the cost of visual fidelity during drag. The default is 200,000. The LOD arrays are rebuilt immediately.

func (*Viewer) SetMaxZoomOutFraction

func (v *Viewer) SetMaxZoomOutFraction(f float64)

SetMaxZoomOutFraction sets the minimum visible fraction of the largest canvas dimension that the point cloud must occupy when zooming out. For example, 0.2 means zoom-out stops when the cloud covers ~20% of the viewport. The default is 0.2.

func (*Viewer) SetOnFrameDrawn

func (v *Viewer) SetOnFrameDrawn(fn func(time.Duration))

SetOnFrameDrawn registers a callback that is invoked at the end of each frame render with the time the draw call took. Useful for benchmarking. If the FPS display is enabled, both the FPS counter and this callback will be called.

Note: calling this multiple times creates a chain of closures; each new callback wraps the previous one. This is fine for single-call usage.

func (*Viewer) SetOrientation

func (v *Viewer) SetOrientation(q Quat)

SetOrientation sets the view orientation to the given quaternion.

func (*Viewer) SetPan

func (v *Viewer) SetPan(x, y float64)

SetPan sets the pan offset in Fyne DIP units.

func (*Viewer) SetPoints

func (v *Viewer) SetPoints(pts []Point3D)

SetPoints replaces the displayed point cloud and resets the view to fit all points. The points should be normalized (see PointCloud.Normalize).

func (*Viewer) SetPointsPreserveView

func (v *Viewer) SetPointsPreserveView(pts []Point3D)

SetPointsPreserveView replaces the displayed point cloud without resetting the orientation, zoom, or pan.

func (*Viewer) SetScale

func (v *Viewer) SetScale(normScale float64)

SetScale sets the normalization scale factor for the scale bar. This is typically the NormScale value from a PointCloud after Normalize().

func (*Viewer) SetScaleBarColor

func (v *Viewer) SetScaleBarColor(c color.RGBA)

SetScaleBarColor sets the scale bar color at runtime.

func (*Viewer) SetScaleUnit

func (v *Viewer) SetScaleUnit(unit string)

SetScaleUnit sets the unit label for the scale bar (e.g. "m").

func (*Viewer) SetScaleUnitScale

func (v *Viewer) SetScaleUnitScale(multiplier float64)

SetScaleUnitScale sets the unit multiplier for the scale bar.

func (*Viewer) SetUpAxis

func (v *Viewer) SetUpAxis(up UpAxis)

SetUpAxis sets the up-axis convention used for rendering. Use YUp for data where Y is up, or ZUp for data where Z is up (typical for LiDAR and surveying data).

func (*Viewer) SetZoom

func (v *Viewer) SetZoom(z float64)

SetZoom sets the zoom level.

func (*Viewer) Zoom

func (v *Viewer) Zoom() float64

Zoom returns the current zoom level.

func (*Viewer) ZoomToExtents

func (v *Viewer) ZoomToExtents()

ZoomToExtents resets zoom and pan so the full point cloud is visible.

Directories

Path Synopsis
_examples
seabed command
Package main generates a synthetic seabed point cloud and displays it.
Package main generates a synthetic seabed point cloud and displays it.
streaming command
Package main demonstrates streaming point cloud data to the viewer widget.
Package main demonstrates streaming point cloud data to the viewer widget.
cmd
pcbench command
pcbench is an end-to-end benchmark for the point cloud viewer.
pcbench is an end-to-end benchmark for the point cloud viewer.
pointcloud command
Package main implements the Point Cloud Viewer application.
Package main implements the Point Cloud Viewer application.
xyz-convert command
Command xyz-convert converts XYZ point cloud files to PLY format.
Command xyz-convert converts XYZ point cloud files to PLY format.
internal
raster
Package raster provides 2D raster drawing primitives.
Package raster provides 2D raster drawing primitives.
ui
Package ui contains internal UI types
Package ui contains internal UI types

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL