Skip to content

Commit

Permalink
Splat and camera updates (#265)
Browse files Browse the repository at this point in the history
* small updates

* small

* v4.7.0
  • Loading branch information
slimbuck committed Oct 27, 2023
1 parent 44cba90 commit 7dd6906
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 44 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "model-viewer",
"version": "4.6.0",
"version": "4.7.0",
"author": "PlayCanvas<[email protected]>",
"homepage": "https://playcanvas.com",
"description": "PlayCanvas glTF Viewer",
Expand Down
38 changes: 25 additions & 13 deletions src/orbit-camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ class OrbitCamera {
focalPoint: SmoothedValue;
azimElevDistance: SmoothedValue;

sceneSize: number;
zoomMin = 0.05;
zoomMax = 10;

constructor(cameraNode: Entity, transitionTime: number) {
this.cameraNode = cameraNode;
this.focalPoint = new SmoothedValue(new Vec3(0, 0, 0), transitionTime);
Expand Down Expand Up @@ -105,7 +109,7 @@ class OrbitCamera {

const aed = this.azimElevDistance.value;
this.calcForwardVec(vec);
vec.mulScalar(aed.z);
vec.mulScalar(Math.max(this.zoomMin, Math.min(this.zoomMax, aed.z)) * this.sceneSize);
vec.add(this.focalPoint.value);

this.cameraNode.setLocalPosition(vec);
Expand Down Expand Up @@ -155,18 +159,20 @@ class OrbitCameraInputMouse {
}

pan(screenPoint: MouseEvent) {
const orbitCamera = this.orbitCamera;

// 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 camera = this.orbitCamera.cameraNode.camera;
const distance = this.orbitCamera.azimElevDistance.value.z;
const camera = orbitCamera.cameraNode.camera;
const distance = orbitCamera.azimElevDistance.value.z * orbitCamera.sceneSize;

camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);

worldDiff.sub2(toWorldPoint, fromWorldPoint);
worldDiff.add(this.orbitCamera.focalPoint.target);
worldDiff.add(orbitCamera.focalPoint.target);

this.orbitCamera.focalPoint.goto(worldDiff);
orbitCamera.focalPoint.goto(worldDiff);
}


Expand Down Expand Up @@ -208,8 +214,10 @@ class OrbitCameraInputMouse {
}

onMouseWheel(event: MouseEvent) {
vec.copy(this.orbitCamera.azimElevDistance.target);
const orbitCamera = this.orbitCamera;
vec.copy(orbitCamera.azimElevDistance.target);
vec.z -= event.wheelDelta * -2 * this.distanceSensitivity * (vec.z * 0.1);
vec.z = Math.max(orbitCamera.zoomMin, Math.min(orbitCamera.zoomMax, vec.z));
this.orbitCamera.azimElevDistance.goto(vec);
event.event.preventDefault();
}
Expand Down Expand Up @@ -282,24 +290,27 @@ class OrbitCameraInputTouch {
}

pan(midPoint: Vec2) {
const orbitCamera = this.orbitCamera;

// 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 camera = this.orbitCamera.cameraNode.camera;
const distance = this.orbitCamera.azimElevDistance.target.z;
const camera = orbitCamera.cameraNode.camera;
const distance = orbitCamera.azimElevDistance.target.z * orbitCamera.sceneSize;

camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);

worldDiff.sub2(toWorldPoint, fromWorldPoint);
worldDiff.add(this.orbitCamera.focalPoint.target);
worldDiff.add(orbitCamera.focalPoint.target);

this.orbitCamera.focalPoint.goto(worldDiff);
orbitCamera.focalPoint.goto(worldDiff);
}

onTouchMove(event: TouchEvent) {
const orbitCamera = this.orbitCamera;
const pinchMidPoint = this.pinchMidPoint;

const aed = this.orbitCamera.azimElevDistance.target.clone();
const aed = orbitCamera.azimElevDistance.target.clone();

// We only care about the first touch for camera rotation. Work out the difference moved since the last event
// and use that to update the camera target position
Expand All @@ -308,7 +319,7 @@ class OrbitCameraInputTouch {
const touch = touches[0];
aed.y -= (touch.y - this.lastTouchPoint.y) * this.orbitSensitivity;
aed.x -= (touch.x - this.lastTouchPoint.x) * this.orbitSensitivity;
this.orbitCamera.azimElevDistance.goto(aed);
orbitCamera.azimElevDistance.goto(aed);
this.lastTouchPoint.set(touch.x, touch.y);
} else if (touches.length === 2) {
// Calculate the difference in pinch distance since the last event
Expand All @@ -317,7 +328,8 @@ class OrbitCameraInputTouch {
this.lastPinchDistance = currentPinchDistance;

aed.z -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (aed.z * 0.1);
this.orbitCamera.azimElevDistance.goto(aed);
aed.z = Math.max(orbitCamera.zoomMin, Math.min(orbitCamera.zoomMax, aed.z));
orbitCamera.azimElevDistance.goto(aed);

// Calculate pan difference
this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
Expand Down
16 changes: 15 additions & 1 deletion src/splat/splat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,26 @@ class Splat {
{ semantic: SEMANTIC_ATTR13, components: 1, type: device.isWebGPU ? TYPE_UINT32 : TYPE_FLOAT32 }
]);

// initialize index data
let indexData;
if (device.isWebGPU) {
indexData = new Uint32Array(numSplats);
for (let i = 0; i < numSplats; ++i) {
indexData[i] = i;
}
} else {
indexData = new Float32Array(numSplats);
for (let i = 0; i < numSplats; ++i) {
indexData[i] = i + 0.2;
}
}

const vertexBuffer = new VertexBuffer(
device,
vertexFormat,
numSplats,
BUFFER_DYNAMIC,
new ArrayBuffer(numSplats * 4)
indexData.buffer
);

this.meshInstance = new MeshInstance(this.mesh, this.material);
Expand Down
43 changes: 16 additions & 27 deletions src/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -914,12 +914,11 @@ class Viewer {
this.initialCameraPosition = null;
} else {
// automatically placed camera position
const radius = bbox.halfExtents.length();
const distance = (radius * 1.4) / Math.sin(0.5 * camera.fov * camera.aspectRatio * math.DEG_TO_RAD);
aed.copy(this.orbitCamera.azimElevDistance.target);
aed.z = distance;
aed.z = 1.4 / Math.sin(0.5 * camera.fov * camera.aspectRatio * math.DEG_TO_RAD);
}

this.orbitCamera.sceneSize = bbox.halfExtents.length();
this.orbitCamera.azimElevDistance[func](aed);
this.orbitCamera.focalPoint[func](focus);
}
Expand Down Expand Up @@ -1560,14 +1559,20 @@ class Viewer {
// dirty everything
this.dirtyWireframe = this.dirtyBounds = this.dirtySkeleton = this.dirtyGrid = this.dirtyNormals = true;

// reset scene offset
this.sceneRoot.setLocalPosition(0, 0, 0);
// calculate scene bounds after first render in order to get accurate morph target and skinned bounds
this.calcSceneBounds(this.sceneBounds);

// we can't refocus the camera here because the scene hierarchy only gets updated
// during render. we must instead set a flag, wait for a render to take place and
// then focus the camera.
this.firstFrame = true;
// offset scene geometry to place it at the origin
this.sceneRoot.setLocalPosition(-this.sceneBounds.center.x, -this.sceneBounds.getMin().y, -this.sceneBounds.center.z);

// set projective skybox radius
this.projectiveSkybox.domeRadius = this.sceneBounds.halfExtents.length() * this.observer.get('skybox.domeProjection.domeRadius');

this.focusCamera();
this.renderNextFrame();

// we perform some special processing on the first frame
this.firstFrame = true;
}

// rebuild the animation state graph
Expand Down Expand Up @@ -1668,6 +1673,7 @@ class Viewer {
// generate and render debug elements on prerender
private onPrerender() {
if (this.firstFrame) {
this.firstFrame = false;
return;
}

Expand Down Expand Up @@ -1797,7 +1803,7 @@ class Viewer {
private onPostrender() {
// resolve the (possibly multisampled) render target
const rt = this.camera.camera.renderTarget;
if (!this.app.graphicsDevice.isWebGPU && !this.firstFrame && rt._samples > 1) {
if (!this.app.graphicsDevice.isWebGPU && rt._samples > 1) {
rt.resolve();
}

Expand All @@ -1807,23 +1813,6 @@ class Viewer {
}

private onFrameend() {
if (this.firstFrame) {
this.firstFrame = false;

// calculate scene bounds after first render in order to get accurate morph target and skinned bounds
this.calcSceneBounds(this.sceneBounds);

// offset scene geometry to place it at the origin
this.sceneRoot.setLocalPosition(-this.sceneBounds.center.x, -this.sceneBounds.getMin().y, -this.sceneBounds.center.z);
// this.sceneRoot.setLocalEulerAngles(-90, -90, 0);

// set projective skybox radius
this.projectiveSkybox.domeRadius = this.sceneBounds.halfExtents.length() * this.observer.get('skybox.domeProjection.domeRadius');

this.focusCamera();
this.renderNextFrame();
}

if (this.loadTimestamp !== null) {
this.observer.set('scene.loadTime', `${Date.now() - this.loadTimestamp}ms`);
this.loadTimestamp = null;
Expand Down

0 comments on commit 7dd6906

Please sign in to comment.