# las

LAS/LAZ point-cloud parsing, writing, and Three.js rendering.

```typescript
import { LasPointCloud, LasPointsGeometry, LasPointsMaterial } from '@yaoc1996/js-tools/las'
```

---

## `class LasPointCloud`

Holds parsed point-cloud data as typed arrays and provides read access per point or attribute. The constructor is private — use `LasPointCloud.parse()` to create instances.

### `LasPointCloud.parse(data, options?) → Promise<LasPointCloud>`

Parses a LAS/LAZ `ArrayBuffer` in a background worker.

#### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `data` | `ArrayBuffer` | — | Raw LAS/LAZ file bytes |
| `options.attributes` | `ExtraAttribute[]` | `[]` | Extra per-point attributes to decode (e.g. `['intensity', 'red']`) |
| `options.wasmPath` | `string` | `""` | Optional path prefix for self-hosted `laz-perf` WASM. Leave unset to use the wasm bundled alongside the worker. |
| `options.onProgress` | `(progress: number) => void` | `undefined` | Progress callback receiving values in `[0, 1]` |

#### Returns

`Promise<LasPointCloud>` — Instance with `x`, `y`, `z` as `Float64Array` and each extra attribute as `Float32Array`.

#### Example

```typescript
import { LasPointCloud } from '@yaoc1996/js-tools/las'

const response = await fetch('/model.laz')
const buffer = await response.arrayBuffer()

const cloud = await LasPointCloud.parse(buffer, {
    attributes: ['intensity', 'red', 'green', 'blue'],
})

console.log(cloud.header.numberOfPointRecords)
```

#### Notes

- The worker is always terminated after the operation completes or throws.
- The `laz-perf` WASM is bundled alongside the worker and resolved automatically. Set `wasmPath` only to self-host the WASM at a different location.

### `getData(attr) → Float64Array | Float32Array`

Returns the typed array for a given attribute. `x`, `y`, `z` return `Float64Array`; all other attributes return `Float32Array`.

#### Parameters

| Name | Type | Description |
|------|------|-------------|
| `attr` | `LasPointBaseAttribute \| ExtraAttribute` | Attribute name |

#### Example

```typescript
const x = cloud.getData('x')   // Float64Array
const intensity = cloud.getData('intensity')  // Float32Array
```

### `getPoint(i) → Record<LasPointBaseAttribute | ExtraAttribute, number>`

Returns a merged record of all attribute values for point at index `i`.

#### Parameters

| Name | Type | Description |
|------|------|-------------|
| `i` | `number` | Zero-based point index |

### `getAttributes() → string[]`

Returns the names of all extra (non-`x`/`y`/`z`) attributes present in the point cloud.

### `write(options?) → Promise<ArrayBuffer | undefined>`

Writes the point cloud back to a LAS/LAZ `ArrayBuffer` in a background worker.

#### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `options.attributes` | `Partial<Record<ExtraAttribute, Float32Array>>` | `{}` | Overrides for attribute data; merged over stored arrays |
| `options.compressed` | `boolean` | `false` | Write as LAZ (compressed) |
| `options.wasmPath` | `string` | `""` | Optional path prefix for self-hosted `laz-perf` WASM. Leave unset to use the wasm bundled alongside the worker. |
| `options.onProgress` | `(progress: number) => void` | `undefined` | Progress callback receiving values in `[0, 1]` |

#### Returns

`Promise<ArrayBuffer | undefined>` — Serialised LAS/LAZ bytes.

#### Example

```typescript
const buffer = await cloud.write({ compressed: true })
```

---

## `class LasPointsGeometry`

A `BufferGeometry` constructed from a `LasPointCloud`. Coordinates are remapped to Three.js space: `x → x - anchor[0]`, `y → z`, `z → -y + anchor[1]`.

### Constructor

```typescript
new LasPointsGeometry(pointCloud?, options?)
```

#### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `pointCloud` | `LasPointCloud` | `undefined` | Source point cloud; if omitted, an empty geometry is created |
| `options.anchor` | `[number, number]` | `[0, 0]` | XY world-space anchor subtracted from the point positions |
| `options.sampleRate` | `number` | `1` | Use every Nth point (e.g. `2` = half the points) |
| `options.withOpacity` | `boolean` | `false` | Adds an `opacity` attribute initialised to all `1.0` |
| `options.withId` | `boolean` | `false` | Adds an `id` attribute where `id[j] = j * sampleRate + 1` |

#### Example

```typescript
import { LasPointsGeometry } from '@yaoc1996/js-tools/las'
import { Points } from 'three'

const geo = new LasPointsGeometry(cloud, { sampleRate: 4, withOpacity: true })
const points = new Points(geo, material)
scene.add(points)
```

---

## `class LasPointsMaterial`

A `ShaderMaterial` for rendering point clouds with per-point colour, opacity, and size attenuation.

### Constructor

```typescript
new LasPointsMaterial(params)
```

#### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `params.colorAttribute` | `Attribute \| [Attribute, Attribute, Attribute]` | — | Single attribute mapped to greyscale, or `[R, G, B]` triple |
| `params.pointSize` | `number` | `1.0` | Base point size in pixels |
| `params.opacity` | `number` | `1.0` | Global opacity |
| `params.sizeAttenuation` | `boolean` | `true` | Scale point size by `1/w` (perspective) |
| `params.vertexOpacity` | `boolean` | `false` | Read per-vertex `opacity` attribute instead of uniform |
| `params.colorAttributeRange` | `[number, number]` | `[0, 1]` | Default normalisation range for all colour channels |
| `params.redAttributeRange` | `[number, number]` | — | Per-channel range override for red |
| `params.greenAttributeRange` | `[number, number]` | — | Per-channel range override for green |
| `params.blueAttributeRange` | `[number, number]` | — | Per-channel range override for blue |

#### Example

```typescript
import { LasPointsMaterial } from '@yaoc1996/js-tools/las'

// Greyscale from intensity
const mat = new LasPointsMaterial({
    colorAttribute: 'intensity',
    colorAttributeRange: [0, 65535],
    pointSize: 2,
})

// RGB colour
const mat2 = new LasPointsMaterial({
    colorAttribute: ['red', 'green', 'blue'],
    colorAttributeRange: [0, 65535],
})
```

### `pointSize` (getter/setter)

Gets or sets the `pointSize` uniform. Setting triggers `uniformsNeedUpdate = true`.

### `colorAttributeRange` (setter)

Sets all three channel range uniforms (`redAttributeRange`, `greenAttributeRange`, `blueAttributeRange`) to the same value and triggers `uniformsNeedUpdate = true`.

---

## Types

```typescript
type LasVersion = '1.2' | '1.3' | '1.4'

type LasPointRecordFormat = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10

type LasPointBaseAttribute = 'x' | 'y' | 'z'

type LasPointExtraAttribute =
    | 'intensity' | 'returnNumber' | 'numberOfReturns'
    | 'scanDirectionFlag' | 'edgeOfFlightLine' | 'classification'
    | 'scanAngleRank_LeftSide' | 'userData' | 'pointSourceID'
    | 'GPSTime' | 'red' | 'green' | 'blue' | 'NIR'
    | 'classificationFlags' | 'scannerChannel' | 'scanAngle'
    | 'wavePacketDescriptorIndex' | 'byteOffsetToWaveFormData'
    | 'waveformPacketSizeInBytes' | 'returnPointWaveformLocation'
    | 'parametricDx' | 'parametricDy' | 'parametricDz'

type LasHeader = {
    fileSignature: string
    versionMajor: number
    versionMinor: number
    numberOfPointRecords: number
    pointDataRecordFormat: LasPointRecordFormat
    pointDataRecordLength: number
    xScaleFactor: number
    yScaleFactor: number
    zScaleFactor: number
    xOffset: number
    yOffset: number
    zOffset: number
    // ... other header fields
}
```
