Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<canvas></canvas>
30 changes: 30 additions & 0 deletions apps/typegpu-docs/src/examples/rendering/heatmap/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as d from 'typegpu/data';

import { Plotter } from './prism/src/plotter.ts';
import { predefinedSurfaces } from './prism/src/examples/surfaces.ts';
import { Scalers } from './prism/src/scalers.ts';

const canvas = document.querySelector('canvas') as HTMLCanvasElement;

const plotter = new Plotter(canvas);
await plotter.init();

plotter.addPlots(
[
predefinedSurfaces.normal,
],
{
xScaler: Scalers.MinMaxScaler,
yScaler: Scalers.SignPreservingScaler,
zScaler: Scalers.MinMaxScaler,
xZeroPlane: false,
zZeroPlane: false,
yZeroPlane: true,
topology: 'all',
basePlanesTranslation: d.vec3f(0, -0.01, 0),
basePlanesScale: d.vec3f(2.01),
basePlotsTranslation: d.vec3f(),
basePlotsScale: d.vec3f(2, 1, 2),
},
);
plotter.startRenderLoop();
5 changes: 5 additions & 0 deletions apps/typegpu-docs/src/examples/rendering/heatmap/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"title": "Heatmap",
"category": "rendering",
"tags": ["experimental", "3d", "rasterization"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as d from 'typegpu/data';

import type {
CameraConfig,
GridConfig,
PlotConfig,
ScaleTransform,
} from './types.ts';
import { Scalers } from './scalers.ts';

export const EPS = 1e-6;
export const DEFAULT_CAMERA_CONFIG: CameraConfig = {
zoomable: true,
draggable: true,
position: d.vec4f(7, 4, 7, 1),
target: d.vec3f(),
up: d.vec3f(0, 1, 0),
fov: Math.PI / 4,
near: 0.1,
far: 1000,
orbitSensitivity: 0.005,
zoomSensitivity: 0.05,
maxZoom: 7,
};

export const DEFAULT_PLOT_CONFIG: PlotConfig = {
xScaler: Scalers.IdentityScaler,
yScaler: Scalers.IdentityScaler,
zScaler: Scalers.IdentityScaler,
xZeroPlane: false,
yZeroPlane: false,
zZeroPlane: false,
topology: 'all',
basePlanesTranslation: d.vec3f(0, -0.01, 0),
basePlanesScale: d.vec3f(2.01),
basePlotsTranslation: d.vec3f(),
basePlotsScale: d.vec3f(2),
};

export const DEFAULT_PLANE_COLOR = d.vec4f(d.vec3f(0.29, 0.21, 0.47), 0.5);
export const PLANE_GRID_CONFIG: GridConfig = {
nx: 2,
nz: 2,
xRange: { min: -1, max: 1 },
zRange: { min: -1, max: 1 },
yCallback: () => 0,
colorCallback: () => DEFAULT_PLANE_COLOR,
edgeColorCallback: () => d.vec4f(),
};
export const IDENTITY_SCALE_TRANSFORM: ScaleTransform = {
offset: d.vec3f(),
scale: d.vec3f(1),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import * as d from 'typegpu/data';
import * as std from 'typegpu/std';
import * as m from 'wgpu-matrix';

import type { CameraConfig } from './types.ts';

export class EventHandler {
#canvas: HTMLCanvasElement;
#isDragging = false;
#prevX = 0;
#prevY = 0;
#orbitRadius: number;
#orbitYaw: number;
#orbitPitch: number;
#cameraConfig: CameraConfig;
#cameraViewMatrix!: d.m4x4f;
#cameraChanged = false;
#handlersMap;

constructor(canvas: HTMLCanvasElement, cameraConfig: CameraConfig) {
this.#canvas = canvas;
this.#cameraConfig = cameraConfig;

const cameraPosition = cameraConfig.position;
this.#orbitRadius = std.length(cameraPosition.xyz);
this.#orbitYaw = Math.atan2(
cameraPosition.x,
cameraPosition.z,
);
this.#orbitPitch = Math.asin(
cameraPosition.y / this.#orbitRadius,
);

this.#handlersMap = new Map();
}

setup() {
const canvas = this.#canvas;
const handlersMap = this.#handlersMap;

handlersMap.set('contextmenu', this.#preventDefault.bind(this));
canvas.addEventListener('contextmenu', handlersMap.get('contextmenu'));

if (this.#cameraConfig.zoomable) {
handlersMap.set('wheel', this.#wheelEventListener.bind(this));
canvas.addEventListener('wheel', handlersMap.get('wheel'), {
passive: false,
});
}

if (this.#cameraConfig.draggable) {
handlersMap.set('mousedown', this.#mouseDownEventListener.bind(this));
canvas.addEventListener('mousedown', this.#handlersMap.get('mousedown'));

handlersMap.set('mousemove', this.#mouseMoveEventListener.bind(this));
canvas.addEventListener('mousemove', this.#handlersMap.get('mousemove'));

handlersMap.set('mouseup', this.#mouseUpEventListener.bind(this));
canvas.addEventListener('mouseup', this.#handlersMap.get('mouseup'));
}
}

resetCameraChangedFlag() {
this.#cameraChanged = false;
}

get cameraChanged() {
return this.#cameraChanged;
}

get cameraViewMatrix() {
return this.#cameraViewMatrix;
}

#getCoordinalesfromPolars(): d.v4f {
const orbitRadius = this.#orbitRadius;
const orbitYaw = this.#orbitYaw;
const orbitPitch = this.#orbitPitch;

return d.vec4f(
orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch),
orbitRadius * Math.sin(orbitPitch),
orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch),
1,
);
}

#updateCameraOrbit(dx: number, dy: number) {
const orbitYaw = this.#orbitYaw - dx * this.#cameraConfig.orbitSensitivity;
const maxPitch = Math.PI / 2 - 0.01;
const orbitPitch = std.clamp(
this.#orbitPitch +
dy * this.#cameraConfig.orbitSensitivity,
-maxPitch,
maxPitch,
);

const newCameraPos = this.#getCoordinalesfromPolars();
const newView = m.mat4.lookAt(
newCameraPos,
this.#cameraConfig.target,
this.#cameraConfig.up,
d.mat4x4f(),
);

this.#orbitYaw = orbitYaw;
this.#orbitPitch = orbitPitch;
this.#cameraChanged = true;
this.#cameraViewMatrix = newView;
}

#updateCameraZoom(dy: number): void {
this.#orbitRadius = Math.max(
this.#cameraConfig.maxZoom,
this.#orbitRadius + dy * this.#cameraConfig.zoomSensitivity,
);

const newCameraPos = this.#getCoordinalesfromPolars();
const newView = m.mat4.lookAt(
newCameraPos,
this.#cameraConfig.target,
this.#cameraConfig.up,
d.mat4x4f(),
);

this.#cameraChanged = true;
this.#cameraViewMatrix = newView;
}

#preventDefault(event: Event) {
event.preventDefault();
}

#mouseUpEventListener() {
this.#isDragging = false;
}

#mouseMoveEventListener(event: MouseEvent) {
if (!this.#isDragging) return;

const dx = event.clientX - this.#prevX;
const dy = event.clientY - this.#prevY;
this.#prevX = event.clientX;
this.#prevY = event.clientY;

this.#updateCameraOrbit(dx, dy);
}

#mouseDownEventListener(event: MouseEvent) {
if (event.button === 0) {
this.#isDragging = true;
}
this.#prevX = event.clientX;
this.#prevY = event.clientY;
}

#wheelEventListener(event: WheelEvent) {
this.#preventDefault(event);
this.#updateCameraZoom(event.deltaY);
}

destroy() {
for (const [event, handler] of this.#handlersMap) {
this.#canvas.removeEventListener(event, handler);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as d from 'typegpu/data';

import * as c from '../constants.ts';
import { GridSurface } from '../grid.ts';

const defaultRasterizationColor = d.vec4f(0, 1, 0.75, 1);

const mistyMountains = new GridSurface({
nx: 49,
nz: 49,
xRange: { min: -5, max: 5 },
zRange: { min: -5, max: 5 },
yCallback: () => Math.random() * 4,
colorCallback: (y: number) => d.vec4f(d.vec3f(y), 1),
edgeColorCallback: (y: number) => defaultRasterizationColor,
});

const logXZ = new GridSurface({
nx: 49,
nz: 49,
xRange: { min: -1, max: 9 },
zRange: { min: -1, max: 9 },
yCallback: (x: number, z: number) => Math.log(Math.abs(x * z) + c.EPS),
colorCallback: (y: number) => d.vec4f(d.vec3f(y), 1),
edgeColorCallback: (y: number) => defaultRasterizationColor,
});

const ripple = new GridSurface({
nx: 101,
nz: 101,
xRange: { min: -1, max: 1 },
zRange: { min: -1, max: 1 },
yCallback: (x: number, z: number) => 1 + Math.sin(10 * (x ** 2 + z ** 2)),
colorCallback: (y: number) => d.vec4f(d.vec3f(y / 3), 1),
edgeColorCallback: (y: number) => defaultRasterizationColor,
});

const normal = new GridSurface({
nx: 101,
nz: 101,
xRange: { min: -5, max: 5 },
zRange: { min: -5, max: 5 },
yCallback: (x: number, z: number) => Math.exp(-(x ** 2 + z ** 2) / 2),
colorCallback: (y: number) => d.vec4f(d.vec3f(y), 1),
edgeColorCallback: (y: number) => defaultRasterizationColor,
});

const powerOfTwo = new GridSurface({
nx: 101,
nz: 101,
xRange: { min: -2, max: 2 },
zRange: { min: -2, max: 2 },
yCallback: (x: number, z: number) => 2 ** Math.abs(x * z),
colorCallback: (y: number) => d.vec4f(0.5, 0, 0, 0.5),
edgeColorCallback: (y: number) => defaultRasterizationColor,
});

const discreteMul = new GridSurface({
nx: 500,
nz: 500,
xRange: { min: -5, max: 5 },
zRange: { min: -5, max: 5 },
yCallback: (x: number, z: number) => Math.floor(x * z),
colorCallback: (y: number) => d.vec4f(d.vec3f(y), 1),
edgeColorCallback: (y: number) => defaultRasterizationColor,
});

export const predefinedSurfaces = {
mistyMountains,
logXZ,
ripple,
normal,
powerOfTwo,
discreteMul,
};
Loading