Skip to content

Commit

Permalink
Camera cleanup (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
slimbuck authored Aug 27, 2024
1 parent 336f8a9 commit 65e77fc
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 91 deletions.
117 changes: 55 additions & 62 deletions src/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
PIXELFORMAT_RGBA8,
PIXELFORMAT_DEPTH,
drawTexture,
BoundingBox,
Color,
Entity,
EventHandler,
Expand Down Expand Up @@ -52,18 +53,16 @@ const mod = (n: number, m: number) => ((n % m) + m) % m;
class Camera extends Element {
controller: PointerController;
entity: Entity;
focalPointTween = new TweenValue({x: 0, y: 0.5, z: 0});
azimElevTween = new TweenValue({azim: 30, elev: -15});
distanceTween = new TweenValue({distance: 2});
focalPointTween = new TweenValue({ x: 0, y: 0.5, z: 0 });
azimElevTween = new TweenValue({ azim: 30, elev: -15 });
distanceTween = new TweenValue({ distance: 2 });

minElev = -90;
maxElev = 90;

events = new EventHandler();

autoRotateTimer = 0;
autoRotateDelayValue = 0;
focusDistance: number;
sceneRadius = 5;

picker: Picker;
pickModeColorBuffer: Texture;
Expand All @@ -84,6 +83,15 @@ class Camera extends Element {
// this.entity.camera.requestSceneColorMap(true);
}

// fov
set fov(value: number) {
this.entity.camera.fov = value;
}

get fov() {
return this.entity.camera.fov;
}

// near clip
set near(value: number) {
this.entity.camera.nearClip = value;
Expand Down Expand Up @@ -146,11 +154,13 @@ class Camera extends Element {
}

setDistance(distance: number, dampingFactorFactor: number = 1) {
const controls = this.scene.config.controls;

// clamp
distance = Math.max(this.scene.config.controls.minZoom, Math.min(this.scene.config.controls.maxZoom, distance));
distance = Math.max(controls.minZoom, Math.min(controls.maxZoom, distance));

const t = this.distanceTween;
t.goto({distance}, dampingFactorFactor * this.scene.config.controls.dampingFactor);
t.goto({ distance }, dampingFactorFactor * controls.dampingFactor);
}

// convert point (relative to camera focus point) to azimuth, elevation, distance
Expand Down Expand Up @@ -191,10 +201,6 @@ class Camera extends Element {
const clr = config.backgroundColor;
this.entity.camera.clearColor.set(clr.r, clr.g, clr.b, clr.a);

// initialize autorotate
this.autoRotateTimer = 0;
this.autoRotateDelayValue = controls.autoRotateInitialDelay;

this.minElev = (controls.minPolarAngle * 180) / Math.PI - 90;
this.maxElev = (controls.maxPolarAngle * 180) / Math.PI - 90;

Expand All @@ -210,13 +216,17 @@ class Camera extends Element {
// exposure
this.scene.app.scene.exposure = config.camera.exposure;

this.fov = config.camera.fov;

// initial camera position and orientation
this.setAzimElev(controls.initialAzim, controls.initialElev, 0);
this.setDistance(controls.initialZoom, 0);

// picker
const { width, height } = this.scene.targetSize;
this.picker = new Picker(this.scene.app, width, height);

this.scene.events.on('scene.boundChanged', this.onBoundChanged, this);
}

remove() {
Expand All @@ -229,11 +239,22 @@ class Camera extends Element {
// destroy doesn't exist on picker?
// this.picker.destroy();
this.picker = null;

this.scene.events.off('scene.boundChanged', this.onBoundChanged, this);
}

// handle the scene's bound changing. the camera must be configured to render
// the entire extents as well as possible.
// also update the existing camera distance to maintain the current view
onBoundChanged(bound: BoundingBox) {
const prevDistance = this.distanceTween.value.distance * this.sceneRadius;
this.sceneRadius = bound.halfExtents.length();
this.setDistance(prevDistance / this.sceneRadius, 0);
}

serialize(serializer: Serializer) {
const camera = this.entity.camera.camera;
serializer.pack(camera.fov);
serializer.pack(this.fov);
serializer.packa(this.entity.getWorldTransform().data);
serializer.pack(this.entity.camera.renderTarget?.width, this.entity.camera.renderTarget?.height);
}
Expand Down Expand Up @@ -303,24 +324,6 @@ class Camera extends Element {
}

onUpdate(deltaTime: number) {
const config = this.scene.config;

// auto rotate
if (config.controls.autoRotate) {
if (this.autoRotateDelayValue > 0) {
this.autoRotateDelayValue = Math.max(0, this.autoRotateDelayValue - deltaTime);
this.autoRotateTimer = 0;
} else {
this.autoRotateTimer += deltaTime;
const rotateSpeed = Math.min(1, Math.pow(this.autoRotateTimer * 0.5 - 1, 5) + 1); // soften the initial rotation speedup
this.setAzimElev(
this.azim + config.controls.autoRotateSpeed * 10 * deltaTime * rotateSpeed,
this.elevation,
0
);
}
}

// controller update
this.controller.update(deltaTime);

Expand All @@ -334,15 +337,14 @@ class Camera extends Element {

calcForwardVec(forwardVec, azimElev.azim, azimElev.elev);
cameraPosition.copy(forwardVec);
cameraPosition.mulScalar(this.focusDistance * (config.camera.dollyZoom ? distance.distance : 1.0));
cameraPosition.mulScalar(distance.distance * this.sceneRadius / this.fovFactor);
cameraPosition.add(this.focalPointTween.value);

this.entity.setLocalPosition(cameraPosition);
this.entity.setLocalEulerAngles(azimElev.elev, azimElev.azim, 0);

this.fitClippingPlanes(this.entity.getLocalPosition(), this.entity.forward);

this.entity.camera.fov = config.camera.fov * (config.camera.dollyZoom ? 1.0 : distance.distance);
this.entity.camera.camera._updateViewProjMat();
}

Expand Down Expand Up @@ -379,31 +381,27 @@ class Camera extends Element {
// device.copyRenderTarget(renderTarget, null, true, false);
}

focus(options?: { sceneRadius?: number, distance?: number, focalPoint?: Vec3}) {
const config = this.scene.config;

let focalPoint: Vec3 = options?.focalPoint;
if (!focalPoint) {
this.scene.elements.forEach((element: any) => {
if (!focalPoint && element.type === ElementType.splat) {
focalPoint = element.focalPoint && element.focalPoint();
focus(options?: { focalPoint: Vec3, radius: number }) {
const getSplatFocalPoint = () => {
for (const element of this.scene.elements) {
if (element.type === ElementType.splat) {
const focalPoint = (element as Splat).focalPoint?.();
if (focalPoint) {
return focalPoint;
}
}
});
}
}
};

const sceneRadius = options?.sceneRadius ?? this.scene.bound.halfExtents.length();
const distance = sceneRadius / Math.sin(config.camera.fov * math.DEG_TO_RAD * 0.5);
const focalPoint = options ? options.focalPoint : (getSplatFocalPoint() ?? this.scene.bound.center);
const focalRadius = options ? options.radius : this.scene.bound.halfExtents.length();

this.setDistance(options?.distance ?? 1.0, 0);
this.setFocalPoint(focalPoint ?? this.scene.bound.center, 0);
this.focusDistance = 1.1 * distance;
this.setDistance(focalRadius / this.sceneRadius, 0);
this.setFocalPoint(focalPoint, 0);
}

notify(event: string, data?: any) {
const config = this.scene.config;

this.events.fire(event, data);
this.autoRotateDelayValue = config.controls.autoRotateDelay;
get fovFactor() {
return Math.sin(this.fov * math.DEG_TO_RAD * 0.5);
}

// interesect the scene at the given screen coordinate and focus the camera on this location
Expand All @@ -418,10 +416,6 @@ class Camera extends Element {

const splats = scene.getElementsByType(ElementType.splat);

const dist = (a: Vec3, b: Vec3) => {
return vecb.sub2(a, b).length();
};

let closestD = 0;
const closestP = new Vec3();
let closestSplat = null;
Expand All @@ -439,14 +433,13 @@ class Camera extends Element {
plane.setFromPointNormal(vec, this.entity.forward);

// create the pick ray in world space
const res = this.entity.camera.screenToWorld(screenX, screenY, 1.0, vec);
vec.sub2(res, cameraPos);
vec.normalize();
this.entity.camera.screenToWorld(screenX, screenY, 1.0, vec);
vec.sub(cameraPos).normalize();
ray.set(cameraPos, vec);

// find intersection
if (plane.intersectsRay(ray, vec)) {
const distance = dist(vec, cameraPos);
const distance = vecb.sub2(vec, cameraPos).length();
if (!closestSplat || distance < closestD) {
closestD = distance;
closestP.copy(vec);
Expand All @@ -457,8 +450,8 @@ class Camera extends Element {
}

if (closestSplat) {
this.setDistance(cameraPos.sub(closestP).length() / this.focusDistance);
this.setFocalPoint(closestP);
this.setDistance(closestD / this.sceneRadius * this.fovFactor);
scene.events.fire('camera.focalPointPicked', {
camera: this,
splat: closestSplat,
Expand Down
4 changes: 2 additions & 2 deletions src/controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class PointerController {
// For panning to work at any zoom level, we use screen point to world projection
// to work out how far we need to pan the pivotEntity in world space
const c = camera.entity.camera;
const distance = camera.focusDistance * camera.distanceTween.value.distance;
const distance = camera.distanceTween.value.distance * camera.sceneRadius / camera.fovFactor;

c.screenToWorld(x, y, distance, fromWorldPoint);
c.screenToWorld(x - dx, y - dy, distance, toWorldPoint);
Expand Down Expand Up @@ -179,7 +179,7 @@ class PointerController {
const z = keys.ArrowDown - keys.ArrowUp;

if (x || z) {
const factor = deltaTime * camera.distance * camera.focusDistance * 20;
const factor = deltaTime * camera.distance * camera.sceneRadius * 20;
const worldTransform = camera.entity.getWorldTransform();
const xAxis = worldTransform.getX().mulScalar(x * factor);
const zAxis = worldTransform.getZ().mulScalar(z * factor);
Expand Down
2 changes: 1 addition & 1 deletion src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S

scene.camera.focus({
focalPoint: vec,
distance: aabb.halfExtents.length() * vec2.x / scene.bound.halfExtents.length()
radius: aabb.halfExtents.length() * vec2.x
});
}
});
Expand Down
11 changes: 0 additions & 11 deletions src/scene-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const sceneConfig = {
pixelScale: 1,
multisample: false,
fov: 50,
dollyZoom: true,
exposure: 1.0,
toneMapping: 'linear',
debug_render: ''
Expand All @@ -32,9 +31,6 @@ const sceneConfig = {
fade: true
},
controls: {
enableRotate: true, // enableOrbit
enablePan: true,
enableZoom: true,
dampingFactor: 0.2,
minPolarAngle: 0,
maxPolarAngle: Math.PI,
Expand All @@ -43,16 +39,9 @@ const sceneConfig = {
initialAzim: -45,
initialElev: -10,
initialZoom: 1.0,
autoRotate: false,
autoRotateSpeed: -2.0,
autoRotateInitialDelay: 0.0,
autoRotateDelay: 5.0,
orbitSensitivity: 0.3,
zoomSensitivity: 0.4
},
animation: {
autoPlay: false
},
debug: {
showBound: false
}
Expand Down
2 changes: 1 addition & 1 deletion src/scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ class Scene {
});

this.boundDirty = false;
this.events.fire('scene.boundChanged');
this.events.fire('scene.boundChanged', this.boundStorage);
}

return this.boundStorage;
Expand Down
24 changes: 10 additions & 14 deletions src/splat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ void main(void)
return;
}
// read data
// read splat data
readData();
vec4 pos;
Expand All @@ -167,11 +167,6 @@ void main(void)
gl_Position = pos;
texCoord = vertex_position.xy;
#ifndef DITHER_NONE
id = float(splatId);
#endif
vertexState = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
vec4 worldPos = matrix_model * vec4(center, 1.0);
Expand All @@ -180,6 +175,10 @@ void main(void)
color = evalColor(modelDir);
#ifndef DITHER_NONE
id = float(splatId);
#endif
#ifdef PICK_PASS
vertexId = splatId;
#endif
Expand All @@ -200,11 +199,6 @@ float PI = 3.14159;
void main(void)
{
if ((vertexState & 4u) == 4u) {
// deleted
discard;
}
float A = dot(texCoord, texCoord);
if (A > 4.0) {
discard;
Expand All @@ -228,7 +222,7 @@ void main(void)
float alpha;
if ((vertexState & 2u) == 2u) {
// hidden
// frozen/hidden
c = vec3(0.0, 0.0, 0.0);
alpha = B * 0.05;
} else {
Expand All @@ -240,14 +234,16 @@ void main(void)
c = color.xyz;
}
alpha = B;
if (ringSize > 0.0) {
// rings mode
if (A < 4.0 - ringSize * 4.0) {
alpha = max(0.05, B);
} else {
alpha = 0.6;
}
} else {
// centers mode
alpha = B;
}
}
Expand Down

0 comments on commit 65e77fc

Please sign in to comment.