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

Gizmo grid and mesh shaders #6036

Merged
merged 13 commits into from
Feb 8, 2024
30 changes: 17 additions & 13 deletions examples/src/examples/misc/gizmos.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ function controls({ observer, ReactPCUI, React, jsx, fragment }) {
*/
async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath, scriptsPath }) {

// Class for handling gizmos
// class for handling gizmos
class GizmoHandler {
/**
* Gizmo type.
Expand Down Expand Up @@ -418,11 +418,11 @@ async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath,
const app = new pc.AppBase(canvas);
app.init(createOptions);

// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
// set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
// ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);

Expand All @@ -449,33 +449,34 @@ async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath,
* @param {pc.Color} color - The color.
* @returns {pc.Material} - The standard material.
*/
function createMaterial(color) {
function createColorMaterial(color) {
const material = new pc.StandardMaterial();
material.diffuse = color;
material.update();
return material;
}

// create entities
const box = new pc.Entity('box');
box.addComponent('render', {
type: 'box',
material: createMaterial(new pc.Color(0.8, 1, 1))
material: createColorMaterial(new pc.Color(0.8, 1, 1))
});
box.setPosition(1, 0, 1);
app.root.addChild(box);

const sphere = new pc.Entity('sphere');
sphere.addComponent('render', {
type: 'sphere',
material: createMaterial(new pc.Color(1, 0.8, 1))
material: createColorMaterial(new pc.Color(1, 0.8, 1))
});
sphere.setPosition(-1, 0, 1);
app.root.addChild(sphere);

const cone = new pc.Entity('cone');
cone.addComponent('render', {
type: 'cone',
material: createMaterial(new pc.Color(1, 1, 0.8))
material: createColorMaterial(new pc.Color(1, 1, 0.8))
});
cone.setPosition(-1, 0, -1);
cone.setLocalScale(1.5, 2.25, 1.5);
Expand All @@ -484,7 +485,7 @@ async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath,
const capsule = new pc.Entity('capsule');
capsule.addComponent('render', {
type: 'capsule',
material: createMaterial(new pc.Color(0.8, 0.8, 1))
material: createColorMaterial(new pc.Color(0.8, 0.8, 1))
});
capsule.setPosition(1, 0, -1);
app.root.addChild(capsule);
Expand Down Expand Up @@ -530,14 +531,15 @@ async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath,
app.root.addChild(keyLight);
keyLight.setEulerAngles(0, 0, -60);

// create gizmoLayer
// create layers
const gizmoLayer = new pc.Layer({
name: 'Gizmo',
clearDepthBuffer: true,
opaqueSortMode: pc.SORTMODE_NONE,
transparentSortMode: pc.SORTMODE_NONE
});
app.scene.layers.push(gizmoLayer);
const layers = app.scene.layers;
layers.push(gizmoLayer);
camera.camera.layers = camera.camera.layers.concat(gizmoLayer.id);

// create gizmo
Expand All @@ -562,6 +564,7 @@ async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath,
window.top.setProj(value);
};

// key event handlers
const keydown = (/** @type {KeyboardEvent} */ e) => {
gizmoHandler.gizmo.snap = !!e.shiftKey;
gizmoHandler.gizmo.uniform = !e.ctrlKey;
Expand Down Expand Up @@ -596,7 +599,7 @@ async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath,
window.addEventListener('keyup', keyup);
window.addEventListener('keypress', keypress);

// Gizmo and camera set handler
// gizmo and camera set handler
const tmpC = new pc.Color();
data.on('*:set', (/** @type {string} */ path, value) => {
const pathArray = path.split('.');
Expand Down Expand Up @@ -635,9 +638,9 @@ async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath,
}
});

// Picker
// picker
const picker = new pc.Picker(app, canvas.clientWidth, canvas.clientHeight);
const worldLayer = app.scene.layers.getLayerByName("World");
const worldLayer = layers.getLayerByName('World');
const pickerLayers = [worldLayer];

const onPointerDown = (/** @type {PointerEvent} */ e) => {
Expand All @@ -660,6 +663,7 @@ async function example({ pcx, canvas, deviceType, data, glslangPath, twgslPath,
};
window.addEventListener('pointerdown', onPointerDown);

// grid
const gridColor = new pc.Color(1, 1, 1, 0.5);
const gridHalfSize = 4;
/**
Expand Down
125 changes: 98 additions & 27 deletions extras/gizmo/axis-shapes.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import {
CULLFACE_NONE,
CULLFACE_BACK,
BLEND_NORMAL,
SEMANTIC_POSITION,
SEMANTIC_COLOR,
createBox,
createCone,
createCylinder,
createPlane,
createMesh,
createTorus,
createShaderFromCode,
Material,
MeshInstance,
Entity,
Color,
Quat,
Vec3
} from 'playcanvas';

import { COLOR_GRAY } from './default-colors.js';
import { MeshTriData } from './mesh-tri-data.js';

// constants
const SHADOW_DAMP_SCALE = 0.25;
const SHADOW_DAMP_OFFSET = 0.75;
const SHADOW_MESH_MAP = new Map();
const TORUS_RENDER_SEGMENTS = 80;
const TORUS_INTERSECT_SEGMENTS = 20;
const LIGHT_DIR = new Vec3(1, 2, 3);
Expand All @@ -27,13 +36,43 @@ const MESH_TEMPLATES = {
plane: createPlane,
torus: createTorus
};
const SHADER = {
vert: /* glsl */`
attribute vec3 vertex_position;
attribute vec4 vertex_color;
varying vec4 vColor;
varying vec2 vZW;
uniform mat4 matrix_model;
uniform mat4 matrix_viewProjection;
void main(void) {
gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
vColor = vertex_color;
#ifdef GL2
// store z/w for later use in fragment shader
vZW = gl_Position.zw;
// disable depth clipping
gl_Position.z = 0.0;
#endif
}`,
frag: /* glsl */`
precision highp float;
varying vec4 vColor;
varying vec2 vZW;
void main(void) {
gl_FragColor = vColor;
#ifdef GL2
// clamp depth in Z to [0, 1] range
gl_FragDepth = max(0.0, min(1.0, (vZW.x / vZW.y + 1.0) * 0.5));
#endif
}`
};

// temporary variables
const tmpV1 = new Vec3();
const tmpV2 = new Vec3();
const tmpQ1 = new Quat();

function createShadowMesh(device, entity, type, templateOpts = {}) {
function createShadowMesh(device, entity, type, color = Color.WHITE, templateOpts = {}) {
const createTemplate = MESH_TEMPLATES[type];
if (!createTemplate) {
throw new Error('Invalid primitive type.');
Expand All @@ -56,24 +95,43 @@ function createShadowMesh(device, entity, type, templateOpts = {}) {
wtm.transformVector(tmpV1, tmpV1);
tmpV1.normalize();
const numVertices = mesh.vertexBuffer.numVertices;
calculateShadowColors(tmpV1, numVertices, options.normals, options.colors);
const shadow = calculateShadow(tmpV1, numVertices, options.normals);
for (let i = 0; i < shadow.length; i++) {
options.colors.push(shadow[i] * color.r * 255, shadow[i] * color.g * 255, shadow[i] * color.b * 255, color.a * 255);
}

return createMesh(device, options.positions, options);
const shadowMesh = createMesh(device, options.positions, options);
SHADOW_MESH_MAP.set(shadowMesh, shadow);

return shadowMesh;
}

function calculateShadowColors(lightDir, numVertices, normals, colors = []) {
function calculateShadow(lightDir, numVertices, normals) {
const shadow = [];
for (let i = 0; i < numVertices; i++) {
const x = normals[i * 3];
const y = normals[i * 3 + 1];
const z = normals[i * 3 + 2];
tmpV2.set(x, y, z);

const dot = lightDir.dot(tmpV2);
const shadow = dot * SHADOW_DAMP_SCALE + SHADOW_DAMP_OFFSET;
colors.push(shadow * 255, shadow * 255, shadow * 255, 1);
shadow.push(dot * SHADOW_DAMP_SCALE + SHADOW_DAMP_OFFSET);
}

return colors;
return shadow;
}

function setShadowMeshColor(mesh, color) {
if (!SHADOW_MESH_MAP.has(mesh)) {
return;
}
const shadow = SHADOW_MESH_MAP.get(mesh);
const colors = [];
for (let i = 0; i < shadow.length; i++) {
colors.push(shadow[i] * color.r * 255, shadow[i] * color.g * 255, shadow[i] * color.b * 255, color.a * 255);
}
mesh.setColors32(colors);
mesh.update();
}

class AxisShape {
Expand All @@ -87,11 +145,13 @@ class AxisShape {

_disabled;

_defaultMaterial;
_defaultColor = Color.WHITE;

_hoverColor = Color.BLACK;

_hoverMaterial;
_disabledColor = COLOR_GRAY;

_disabledMaterial;
_cull = CULLFACE_BACK;

device;

Expand All @@ -114,25 +174,20 @@ class AxisShape {

this._layers = options.layers ?? this._layers;

if (!(options.defaultMaterial instanceof Material)) {
throw new Error('No default material provided.');
if (options.defaultColor instanceof Color) {
this._defaultColor = options.defaultColor;
}
this._defaultMaterial = options.defaultMaterial;

if (!(options.hoverMaterial instanceof Material)) {
throw new Error('No hover material provided.');
if (options.hoverColor instanceof Color) {
this._hoverColor = options.hoverColor;
}
this._hoverMaterial = options.hoverMaterial;

if (!(options.disabledMaterial instanceof Material)) {
throw new Error('No disabled material provided.');
if (options.disabledColor instanceof Color) {
this._disabledColor = options.disabledColor;
}
this._disabledMaterial = options.disabledMaterial;
}

set disabled(value) {
for (let i = 0; i < this.meshInstances.length; i++) {
this.meshInstances[i].material = value ? this._disabledMaterial : this._defaultMaterial;
setShadowMeshColor(this.meshInstances[i].mesh, this._disabledColor);
}
this._disabled = value ?? false;
}
Expand All @@ -153,9 +208,20 @@ class AxisShape {
}

_addRenderMeshes(entity, meshes) {
const shader = createShaderFromCode(this.device, SHADER.vert, SHADER.frag, 'axis-shape', {
vertex_position: SEMANTIC_POSITION,
vertex_color: SEMANTIC_COLOR
});

const material = new Material();
material.shader = shader;
material.cull = this._cull;
material.blendType = BLEND_NORMAL;
material.update();

const meshInstances = [];
for (let i = 0; i < meshes.length; i++) {
const mi = new MeshInstance(meshes[i], this._disabled ? this._disabledMaterial : this._defaultMaterial);
const mi = new MeshInstance(meshes[i], material);
meshInstances.push(mi);
this.meshInstances.push(mi);
}
Expand All @@ -167,17 +233,19 @@ class AxisShape {
}

_addRenderShadowMesh(entity, type) {
const mesh = createShadowMesh(this.device, entity, type);
const color = this._disabled ? this._disabledColor : this._defaultColor;
const mesh = createShadowMesh(this.device, entity, type, color);
this._addRenderMeshes(entity, [mesh]);
}

hover(state) {
if (this._disabled) {
return;
}
const material = state ? this._hoverMaterial : this._defaultMaterial;

for (let i = 0; i < this.meshInstances.length; i++) {
this.meshInstances[i].material = material;
const color = state ? this._hoverColor : this._defaultColor;
setShadowMeshColor(this.meshInstances[i].mesh, color);
}
}

Expand Down Expand Up @@ -501,7 +569,8 @@ class AxisDisk extends AxisShape {
}

_createRenderTorus(sectorAngle) {
return createShadowMesh(this.device, this.entity, 'torus', {
const color = this._disabled ? this._disabledColor : this._defaultColor;
return createShadowMesh(this.device, this.entity, 'torus', color, {
tubeRadius: this._tubeRadius,
ringRadius: this._ringRadius,
sectorAngle: sectorAngle,
Expand Down Expand Up @@ -573,6 +642,8 @@ class AxisDisk extends AxisShape {
}

class AxisPlane extends AxisShape {
_cull = CULLFACE_NONE;

_size = 0.2;

_gap = 0.1;
Expand Down
7 changes: 7 additions & 0 deletions extras/gizmo/default-colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Color } from 'playcanvas';

export const COLOR_RED = Object.freeze(new Color(1, 0.3, 0.3));
export const COLOR_GREEN = Object.freeze(new Color(0.3, 1, 0.3));
export const COLOR_BLUE = Object.freeze(new Color(0.3, 0.3, 1));
export const COLOR_YELLOW = Object.freeze(new Color(1, 1, 0.5));
export const COLOR_GRAY = Object.freeze(new Color(0.5, 0.5, 0.5, 0.5));
Loading