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 selection outline #221

Merged
merged 9 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
22 changes: 6 additions & 16 deletions src/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,7 @@ class Camera extends Element {
super(ElementType.camera);
// create the camera entity
this.entity = new Entity('Camera');
this.entity.addComponent('camera', {
fov: 60,
clearColor: new Color(0, 0, 0, 0),
frustumCulling: true
});
this.entity.addComponent('camera');

// NOTE: this call is needed for refraction effect to work correctly, but
// it slows rendering and should only be made when required.
Expand Down Expand Up @@ -269,8 +265,7 @@ class Camera extends Element {

// out with the old
if (rt) {
rt.colorBuffer.destroy();
rt.depthBuffer.destroy();
rt.destroyTextureBuffers();
rt.destroy();

this.pickModeRenderTarget.destroy();
Expand All @@ -289,16 +284,12 @@ class Camera extends Element {
};

// in with the new
const pixelFormat = PIXELFORMAT_RGBA8;
const samples = this.scene.config.camera.multisample ? device.maxSamples : 1;

const colorBuffer = createTexture('cameraColor', width, height, pixelFormat);
const colorBuffer = createTexture('cameraColor', width, height, PIXELFORMAT_RGBA8);
const depthBuffer = createTexture('cameraDepth', width, height, PIXELFORMAT_DEPTH);
const renderTarget = new RenderTarget({
colorBuffer,
depthBuffer,
flipY: false,
samples,
autoResolve: false
});
this.entity.camera.renderTarget = renderTarget;
Expand All @@ -307,13 +298,10 @@ class Camera extends Element {
// create pick mode render target (reuse color buffer)
this.pickModeRenderTarget = new RenderTarget({
colorBuffer,
depth: false,
flipY: false,
samples,
autoResolve: false
});

this.scene.events.fire('camera.resize', {width, height});
this.scene.events.fire('camera.resize', { width, height });
}

onUpdate(deltaTime: number) {
Expand Down Expand Up @@ -366,6 +354,8 @@ class Camera extends Element {
renderTarget.resolve(true, false);
}

this.scene.events.fire('camera.preResolve');

// copy render target
device.copyRenderTarget(renderTarget, null, true, false);
}
Expand Down
23 changes: 23 additions & 0 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S
scene.forceRender = true;
});

events.on('view.outlineSelection', () => {
scene.forceRender = true;
});

events.on('view.bands', (bands: number) => {
scene.forceRender = true;
});
Expand Down Expand Up @@ -458,6 +462,25 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S
setSplatSize(value);
});

// outline selection

let outlineSelection = false;

const setOutlineSelection = (value: boolean) => {
if (value !== outlineSelection) {
outlineSelection = value;
events.fire('view.outlineSelection', outlineSelection);
}
};

events.function('view.outlineSelection', () => {
return outlineSelection;
});

events.on('view.setOutlineSelection', (value: boolean) => {
setOutlineSelection(value);
});

// view spherical harmonic bands

let viewBands = scene.config.show.shBands;
Expand Down
160 changes: 7 additions & 153 deletions src/infinite-grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,154 +15,7 @@ import {
} from 'playcanvas';
import { Element, ElementType } from './element';
import { Serializer } from './serializer';

const vsCode = /*glsl*/ `
uniform mat4 camera_matrix;
uniform vec2 camera_params;

attribute vec2 vertex_position;

varying vec3 worldFar;

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;
}
`;

const fsCode = /*glsl*/ `
uniform vec3 camera_position;
uniform mat4 camera_viewProjection;
uniform sampler2D blueNoiseTex32;

varying vec3 worldFar;

bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
float d = dot(dir, plane.xyz);
if (abs(d) < 1e-06) {
return false;
}

float n = -(dot(pos, plane.xyz) + plane.w) / d;
if (n < 0.0) {
return false;
}

t = n;

return true;
}

// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c
float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) {
vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y)));
bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5);
vec2 targetWidth = vec2(
invertLine.x ? 1.0 - lineWidth.x : lineWidth.x,
invertLine.y ? 1.0 - lineWidth.y : lineWidth.y
);
vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));
vec2 lineAA = uvDeriv * 1.5;
vec2 gridUV = abs(fract(uv) * 2.0 - 1.0);
gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x;
gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y;
vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);

grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);
grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0));
grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x;
grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y;

return mix(grid2.x, 1.0, grid2.y);
}

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

bool writeDepth(float alpha) {
vec2 uv = fract(gl_FragCoord.xy / 32.0);
float noise = texture2DLodEXT(blueNoiseTex32, uv, 0.0).y;
return alpha > noise;
}

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

// intersect ray with the world xz plane
float t;
if (!intersectPlane(t, p, v, vec4(0, 1, 0, 0))) {
discard;
}

// calculate grid intersection
vec3 pos = p + v * t;
vec2 ddx = dFdx(pos.xz);
vec2 ddy = dFdy(pos.xz);

float epsilon = 1.0 / 255.0;

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

vec3 levelPos;
float levelSize;
float levelAlpha;

// 10m grid with colored main axes
levelPos = pos * 0.1;
levelSize = 2.0 / 1000.0;
levelAlpha = pristineGrid(levelPos.xz, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade;
if (levelAlpha > epsilon) {
vec3 color;
vec2 loc = max(vec2(0.0), abs(levelPos.xz) - abs(ddx * 0.1) - abs(ddy * 0.1));
if (loc.x < levelSize) {
if (loc.y < levelSize) {
color = vec3(1.0);
} else {
color = vec3(0.2, 0.2, 1.0);
}
} else if (loc.y < levelSize) {
color = vec3(1.0, 0.2, 0.2);
} else {
color = vec3(0.9);
}
gl_FragColor = vec4(color, levelAlpha);
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(pos) : 1.0;
return;
}

// 1m grid
levelPos = pos;
levelSize = 1.0 / 100.0;
levelAlpha = pristineGrid(levelPos.xz, ddx, ddy, vec2(levelSize)) * fade;
if (levelAlpha > epsilon) {
gl_FragColor = vec4(vec3(0.7), levelAlpha);
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(pos) : 1.0;
return;
}

// 0.1m grid
levelPos = pos * 10.0;
levelSize = 1.0 / 100.0;
levelAlpha = pristineGrid(levelPos.xz, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade;
if (levelAlpha > epsilon) {
gl_FragColor = vec4(vec3(0.7), levelAlpha);
gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(pos) : 1.0;
return;
}

discard;
}
`;
import { vertexShader, fragmentShader } from './shaders/infinite-grid-shader';

const calcHalfSize = (fov: number, aspect: number, fovIsHorizontal: boolean) => {
let x, y;
Expand All @@ -176,10 +29,6 @@ const calcHalfSize = (fov: number, aspect: number, fovIsHorizontal: boolean) =>
return [ x, y ];
};

const attributes = {
vertex_position: SEMANTIC_POSITION
};

class InfiniteGrid extends Element {
shader: Shader;
quadRender: QuadRender;
Expand All @@ -195,7 +44,10 @@ class InfiniteGrid extends Element {
add() {
const device = this.scene.app.graphicsDevice;

this.shader = createShaderFromCode(device, vsCode, fsCode, 'infinite-grid', attributes);
this.shader = createShaderFromCode(device, vertexShader, fragmentShader, 'infinite-grid', {
vertex_position: SEMANTIC_POSITION
});

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

const cameraMatrixId = device.scope.resolve('camera_matrix');
Expand Down Expand Up @@ -237,6 +89,8 @@ class InfiniteGrid extends Element {

remove() {
this.scene.debugLayer.onPreRenderOpaque = null;
this.shader.destroy();
this.quadRender.destroy();
}

serialize(serializer: Serializer): void {
Expand Down
Loading
Loading