Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add orthographic view #245

Merged
merged 10 commits into from
Oct 29, 2024
Merged
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
10 changes: 5 additions & 5 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
Expand Up @@ -26,7 +26,7 @@
},
"devDependencies": {
"@playcanvas/eslint-config": "^1.7.4",
"@playcanvas/pcui": "^4.5.0",
"@playcanvas/pcui": "^4.5.1",
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-json": "^6.1.0",
Expand Down
86 changes: 80 additions & 6 deletions src/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import {
BoundingBox,
Entity,
EventHandler,
Mat4,
Picker,
Plane,
Ray,
RenderTarget,
Texture,
Vec3,
Vec4,
WebglGraphicsDevice,
PROJECTION_ORTHOGRAPHIC,
PROJECTION_PERSPECTIVE,
TONEMAP_LINEAR,
TONEMAP_FILMIC,
TONEMAP_HEJL,
Expand Down Expand Up @@ -44,6 +48,18 @@ const plane = new Plane();
const ray = new Ray();
const vec = new Vec3();
const vecb = new Vec3();
const va = new Vec3();
const vb = new Vec3();
const vc = new Vec3();
const v4 = new Vec4();

// homogenous matrix-vector multiplication
const hmul = (vec: Vec3, mat: Mat4) => {
v4.set(vec.x, vec.y, vec.z, 1);
mat.transformVec4(v4, v4);
vec.set(v4.x / v4.w, v4.y / v4.w, v4.z / v4.w);
return vec;
};

// modulo dealing with negative numbers
const mod = (n: number, m: number) => ((n % m) + m) % m;
Expand All @@ -65,6 +81,8 @@ class Camera extends Element {
picker: Picker;
pickModeRenderTarget: RenderTarget;

updateCameraUniforms: () => void;

constructor() {
super(ElementType.camera);
// create the camera entity
Expand All @@ -76,6 +94,18 @@ class Camera extends Element {
// this.entity.camera.requestSceneColorMap(true);
}

// ortho
set ortho(value: boolean) {
if (value !== this.ortho) {
this.entity.camera.projection = value ? PROJECTION_ORTHOGRAPHIC : PROJECTION_PERSPECTIVE;
this.scene.events.fire('camera.ortho', value);
}
}

get ortho() {
return this.entity.camera.projection === PROJECTION_ORTHOGRAPHIC;
}

// fov
set fov(value: number) {
this.entity.camera.fov = value;
Expand Down Expand Up @@ -144,6 +174,9 @@ class Camera extends Element {
} else if (t.source.azim - azim > 180) {
t.source.azim -= 360;
}

// return to perspective mode on rotation
this.ortho = false;
}

setDistance(distance: number, dampingFactorFactor: number = 1) {
Expand Down Expand Up @@ -220,6 +253,37 @@ class Camera extends Element {
this.picker = new Picker(this.scene.app, width, height);

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

// prepare camera-specific uniforms
this.updateCameraUniforms = () => {
const device = this.scene.graphicsDevice;
const entity = this.entity;
const camera = entity.camera;

const inv = new Mat4().mul2(camera.projectionMatrix, camera.viewMatrix).invert();

const set = (name: string, vec: Vec3) => {
device.scope.resolve(name).setValue([vec.x, vec.y, vec.z]);
};

// near
if (camera.projection === PROJECTION_PERSPECTIVE) {
// perspective
set('near_origin', entity.getPosition());
set('near_x', Vec3.ZERO);
set('near_y', Vec3.ZERO);
} else {
// orthographic
set('near_origin', hmul(va.set(0, 0, -10), inv));
set('near_x', hmul(vb.set(1, 0, -10), inv).sub(va));
set('near_y', hmul(vc.set(0, 1, -10), inv).sub(va));
}

// far
set('far_origin', hmul(va.set(0, 0, 1), inv));
set('far_x', hmul(vb.set(1, 0, 1), inv).sub(va));
set('far_y', hmul(vc.set(0, 1, 1), inv).sub(va));
};
}

remove() {
Expand Down Expand Up @@ -326,7 +390,9 @@ class Camera extends Element {

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

this.entity.camera.camera._updateViewProjMat();
const { camera } = this.entity;
camera.orthoHeight = 0.5 * this.distanceTween.value.distance * this.sceneRadius / this.fovFactor * (camera.camera.horizontalFov ? this.scene.targetSize.height / this.scene.targetSize.width : 1);
camera.camera._updateViewProjMat();
}

fitClippingPlanes(cameraPosition: Vec3, forwardVec: Vec3) {
Expand All @@ -343,6 +409,7 @@ class Camera extends Element {

onPreRender() {
this.rebuildRenderTargets();
this.updateCameraUniforms();
}

onPostRender() {
Expand Down Expand Up @@ -383,7 +450,7 @@ class Camera extends Element {
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
// intersect the scene at the given screen coordinate and focus the camera on this location
pickFocalPoint(screenX: number, screenY: number) {
const scene = this.scene;
const cameraPos = this.entity.getPosition();
Expand Down Expand Up @@ -412,13 +479,20 @@ class Camera extends Element {
plane.setFromPointNormal(vec, this.entity.forward);

// create the pick ray in world space
this.entity.camera.screenToWorld(screenX, screenY, 1.0, vec);
vec.sub(cameraPos).normalize();
ray.set(cameraPos, vec);
if (this.ortho) {
this.entity.camera.screenToWorld(screenX, screenY, -1.0, vec);
this.entity.camera.screenToWorld(screenX, screenY, 1.0, vecb);
vecb.sub(vec).normalize();
ray.set(vec, vecb);
} else {
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 = vecb.sub2(vec, cameraPos).length();
const distance = vecb.sub2(vec, ray.origin).length();
if (!closestSplat || distance < closestD) {
closestD = distance;
closestP.copy(vec);
Expand Down
4 changes: 3 additions & 1 deletion src/controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ class PointerController {
const wheel = (event: WheelEvent) => {
const sign = (v: number) => v > 0 ? 1 : v < 0 ? -1 : 0;
zoom(sign(event.deltaY) * -0.2);
orbit(sign(event.deltaX) * 2.0, 0);
if (Math.abs(event.deltaX) > 1e-6) {
orbit(sign(event.deltaX) * 2.0, 0);
}
};

// FIXME: safari sends canvas as target of dblclick event but chrome sends the target element
Expand Down
3 changes: 3 additions & 0 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S
case 'ny': scene.camera.setAzimElev(0, 90); break;
case 'nz': scene.camera.setAzimElev(180, 0); break;
}

// switch to ortho mode
scene.camera.ortho = true;
});

events.on('select.all', () => {
Expand Down
33 changes: 8 additions & 25 deletions src/infinite-grid.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import {
BLENDMODE_ONE,
BLENDMODE_ONE_MINUS_SRC_ALPHA,
BLENDMODE_SRC_ALPHA,
BLENDEQUATION_ADD,
CULLFACE_NONE,
FUNC_LESSEQUAL,
PROJECTION_PERSPECTIVE,
SEMANTIC_POSITION,
createShaderFromCode,
BlendState,
DepthState,
Mat4,
QuadRender,
Shader,
createShaderFromCode,
FUNC_LESSEQUAL,
BLENDMODE_ONE,
BLENDMODE_ONE_MINUS_SRC_ALPHA,
BLENDMODE_SRC_ALPHA,
BLENDEQUATION_ADD
Vec4,
} from 'playcanvas';
import { Element, ElementType } from './element';
import { Serializer } from './serializer';
Expand Down Expand Up @@ -50,11 +52,6 @@ class InfiniteGrid extends Element {

this.quadRender = new QuadRender(this.shader);

const cameraMatrixId = device.scope.resolve('camera_matrix');
const cameraParamsId = device.scope.resolve('camera_params');
const cameraPositionId = device.scope.resolve('camera_position');
const cameraViewProjectionId = device.scope.resolve('camera_viewProjection');

const blendState = new BlendState(
true,
BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA,
Expand All @@ -68,20 +65,6 @@ class InfiniteGrid extends Element {
device.setDepthState(DepthState.WRITEDEPTH);
device.setStencilState(null, null);

const cameraEntity = this.scene.camera.entity;
const camera = cameraEntity.camera;

// update viewProjectionInverse matrix
const cameraMatrix = cameraEntity.getWorldTransform().clone();
const cameraParams = calcHalfSize(camera.fov, camera.aspectRatio, camera.horizontalFov);
const cameraPosition = cameraMatrix.getTranslation();
const cameraViewProjection = new Mat4().mul2(camera.projectionMatrix, camera.viewMatrix);

cameraMatrixId.setValue(cameraMatrix.data);
cameraParamsId.setValue(cameraParams);
cameraPositionId.setValue([cameraPosition.x, cameraPosition.y, cameraPosition.z]);
cameraViewProjectionId.setValue(cameraViewProjection.data);

this.quadRender.render();
}
};
Expand Down
1 change: 1 addition & 0 deletions src/outline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class Outline extends Element {
dst.fov = src.fov;
dst.nearClip = src.nearClip;
dst.farClip = src.farClip;
dst.orthoHeight = src.orthoHeight;
}

rebuildRenderTargets(width: number, height: number) {
Expand Down
29 changes: 17 additions & 12 deletions src/shaders/infinite-grid-shader.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
const vertexShader = /*glsl*/ `
uniform mat4 camera_matrix;
uniform vec2 camera_params;
uniform vec3 near_origin;
uniform vec3 near_x;
uniform vec3 near_y;

uniform vec3 far_origin;
uniform vec3 far_x;
uniform vec3 far_y;

attribute vec2 vertex_position;

varying vec3 worldFar;
varying vec3 worldNear;

void main(void) {
gl_Position = vec4(vertex_position, 0.0, 1.0);

vec4 v = camera_matrix * vec4(vertex_position * camera_params, -1.0, 1.0);

worldFar = v.xyz;
worldNear = near_origin + near_x * vertex_position.x + near_y * vertex_position.y;
worldFar = far_origin + far_x * vertex_position.x + far_y * vertex_position.y;
}
`;

const fragmentShader = /*glsl*/ `
uniform vec3 camera_position;
uniform mat4 camera_viewProjection;
uniform vec3 view_position;
uniform mat4 matrix_viewProjection;
uniform sampler2D blueNoiseTex32;

varying vec3 worldNear;
varying vec3 worldFar;

bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
Expand Down Expand Up @@ -62,7 +67,7 @@ const fragmentShader = /*glsl*/ `
}

float calcDepth(vec3 p) {
vec4 v = camera_viewProjection * vec4(p, 1.0);
vec4 v = matrix_viewProjection * vec4(p, 1.0);
return (v.z / v.w) * 0.5 + 0.5;
}

Expand All @@ -73,8 +78,8 @@ const fragmentShader = /*glsl*/ `
}

void main(void) {
vec3 p = camera_position;
vec3 v = normalize(worldFar - camera_position);
vec3 p = worldNear;
vec3 v = normalize(worldFar - worldNear);

// intersect ray with the world xz plane
float t;
Expand All @@ -90,7 +95,7 @@ const fragmentShader = /*glsl*/ `
float epsilon = 1.0 / 255.0;

// calculate fade
float fade = 1.0 - smoothstep(400.0, 1000.0, length(pos - camera_position));
float fade = 1.0 - smoothstep(400.0, 1000.0, length(pos - view_position));
if (fade < epsilon) {
discard;
}
Expand Down
Loading
Loading