diff --git a/examples/src/examples/gizmos/transform-rotate.controls.mjs b/examples/src/examples/gizmos/transform-rotate.controls.mjs index 65eda075168..ea1f62f5685 100644 --- a/examples/src/examples/gizmos/transform-rotate.controls.mjs +++ b/examples/src/examples/gizmos/transform-rotate.controls.mjs @@ -5,7 +5,7 @@ import * as pc from 'playcanvas'; * @returns {JSX.Element} The returned JSX Element. */ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { - const { BindingTwoWay, LabelGroup, Panel, ColorPicker, SliderInput, SelectInput } = ReactPCUI; + const { BindingTwoWay, LabelGroup, Panel, ColorPicker, SliderInput, SelectInput, BooleanInput } = ReactPCUI; const [proj, setProj] = React.useState(pc.PROJECTION_PERSPECTIVE); @@ -84,6 +84,15 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { max: 1, precision: 2 }) + ), + jsx( + LabelGroup, + { text: 'Shading' }, + jsx(BooleanInput, { + type: 'toggle', + binding: new BindingTwoWay(), + link: { observer, path: 'gizmo.shading' } + }) ) ), jsx( diff --git a/examples/src/examples/gizmos/transform-rotate.example.mjs b/examples/src/examples/gizmos/transform-rotate.example.mjs index e9b12151c56..4f43bf31501 100644 --- a/examples/src/examples/gizmos/transform-rotate.example.mjs +++ b/examples/src/examples/gizmos/transform-rotate.example.mjs @@ -88,20 +88,10 @@ light.addComponent('light'); app.root.addChild(light); light.setEulerAngles(0, 0, -60); -// create layers -const gizmoLayer = new pc.Layer({ - name: 'Gizmo', - clearDepthBuffer: true, - opaqueSortMode: pc.SORTMODE_NONE, - transparentSortMode: pc.SORTMODE_NONE -}); -const layers = app.scene.layers; -layers.push(gizmoLayer); -camera.camera.layers = camera.camera.layers.concat(gizmoLayer.id); - // create gizmo -const gizmo = new pc.RotateGizmo(app, camera.camera, gizmoLayer); -gizmo.attach([box]); +const layer = pc.Gizmo.createLayer(app); +const gizmo = new pc.RotateGizmo(camera.camera, layer); +gizmo.attach(box); data.set('gizmo', { size: gizmo.size, snapIncrement: gizmo.snapIncrement, @@ -109,6 +99,7 @@ data.set('gizmo', { yAxisColor: Object.values(gizmo.yAxisColor), zAxisColor: Object.values(gizmo.zAxisColor), colorAlpha: gizmo.colorAlpha, + shading: gizmo.shading, coordSpace: gizmo.coordSpace, ringTolerance: gizmo.ringTolerance, xyzTubeRadius: gizmo.xyzTubeRadius, diff --git a/examples/src/examples/gizmos/transform-scale.controls.mjs b/examples/src/examples/gizmos/transform-scale.controls.mjs index 02df103d7ac..aaae894d137 100644 --- a/examples/src/examples/gizmos/transform-scale.controls.mjs +++ b/examples/src/examples/gizmos/transform-scale.controls.mjs @@ -5,7 +5,7 @@ import * as pc from 'playcanvas'; * @returns {JSX.Element} The returned JSX Element. */ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { - const { BindingTwoWay, LabelGroup, Panel, ColorPicker, SliderInput, SelectInput } = ReactPCUI; + const { BindingTwoWay, LabelGroup, Panel, ColorPicker, SliderInput, SelectInput, BooleanInput } = ReactPCUI; const [proj, setProj] = React.useState(pc.PROJECTION_PERSPECTIVE); @@ -72,6 +72,15 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { max: 1, precision: 2 }) + ), + jsx( + LabelGroup, + { text: 'Shading' }, + jsx(BooleanInput, { + type: 'toggle', + binding: new BindingTwoWay(), + link: { observer, path: 'gizmo.shading' } + }) ) ), jsx( diff --git a/examples/src/examples/gizmos/transform-scale.example.mjs b/examples/src/examples/gizmos/transform-scale.example.mjs index 220b725bbb4..87eefeeb4ac 100644 --- a/examples/src/examples/gizmos/transform-scale.example.mjs +++ b/examples/src/examples/gizmos/transform-scale.example.mjs @@ -88,20 +88,10 @@ light.addComponent('light'); app.root.addChild(light); light.setEulerAngles(0, 0, -60); -// create layers -const gizmoLayer = new pc.Layer({ - name: 'Gizmo', - clearDepthBuffer: true, - opaqueSortMode: pc.SORTMODE_NONE, - transparentSortMode: pc.SORTMODE_NONE -}); -const layers = app.scene.layers; -layers.push(gizmoLayer); -camera.camera.layers = camera.camera.layers.concat(gizmoLayer.id); - // create gizmo -const gizmo = new pc.ScaleGizmo(app, camera.camera, gizmoLayer); -gizmo.attach([box]); +const layer = pc.Gizmo.createLayer(app); +const gizmo = new pc.ScaleGizmo(camera.camera, layer); +gizmo.attach(box); data.set('gizmo', { size: gizmo.size, snapIncrement: gizmo.snapIncrement, @@ -109,6 +99,7 @@ data.set('gizmo', { yAxisColor: Object.values(gizmo.yAxisColor), zAxisColor: Object.values(gizmo.zAxisColor), colorAlpha: gizmo.colorAlpha, + shading: gizmo.shading, coordSpace: gizmo.coordSpace, axisLineTolerance: gizmo.axisLineTolerance, axisCenterTolerance: gizmo.axisCenterTolerance, diff --git a/examples/src/examples/gizmos/transform-translate.controls.mjs b/examples/src/examples/gizmos/transform-translate.controls.mjs index 05224fca59f..69e75853f3c 100644 --- a/examples/src/examples/gizmos/transform-translate.controls.mjs +++ b/examples/src/examples/gizmos/transform-translate.controls.mjs @@ -5,7 +5,7 @@ import * as pc from 'playcanvas'; * @returns {JSX.Element} The returned JSX Element. */ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { - const { BindingTwoWay, LabelGroup, Panel, ColorPicker, SliderInput, SelectInput } = ReactPCUI; + const { BindingTwoWay, LabelGroup, Panel, ColorPicker, SliderInput, SelectInput, BooleanInput } = ReactPCUI; const [proj, setProj] = React.useState(pc.PROJECTION_PERSPECTIVE); @@ -84,6 +84,15 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { max: 1, precision: 2 }) + ), + jsx( + LabelGroup, + { text: 'Shading' }, + jsx(BooleanInput, { + type: 'toggle', + binding: new BindingTwoWay(), + link: { observer, path: 'gizmo.shading' } + }) ) ), jsx( diff --git a/examples/src/examples/gizmos/transform-translate.example.mjs b/examples/src/examples/gizmos/transform-translate.example.mjs index 81a1edf4577..956e9cdd0e2 100644 --- a/examples/src/examples/gizmos/transform-translate.example.mjs +++ b/examples/src/examples/gizmos/transform-translate.example.mjs @@ -88,20 +88,10 @@ light.addComponent('light'); app.root.addChild(light); light.setEulerAngles(0, 0, -60); -// create layers -const gizmoLayer = new pc.Layer({ - name: 'Gizmo', - clearDepthBuffer: true, - opaqueSortMode: pc.SORTMODE_NONE, - transparentSortMode: pc.SORTMODE_NONE -}); -const layers = app.scene.layers; -layers.push(gizmoLayer); -camera.camera.layers = camera.camera.layers.concat(gizmoLayer.id); - // create gizmo -const gizmo = new pc.TranslateGizmo(app, camera.camera, gizmoLayer); -gizmo.attach([box]); +const layer = pc.Gizmo.createLayer(app); +const gizmo = new pc.TranslateGizmo(camera.camera, layer); +gizmo.attach(box); data.set('gizmo', { size: gizmo.size, snapIncrement: gizmo.snapIncrement, @@ -109,6 +99,7 @@ data.set('gizmo', { yAxisColor: Object.values(gizmo.yAxisColor), zAxisColor: Object.values(gizmo.zAxisColor), colorAlpha: gizmo.colorAlpha, + shading: gizmo.shading, coordSpace: gizmo.coordSpace, axisLineTolerance: gizmo.axisLineTolerance, axisCenterTolerance: gizmo.axisCenterTolerance, diff --git a/examples/src/examples/misc/editor.example.mjs b/examples/src/examples/misc/editor.example.mjs index 107f0da87e7..851959c20b2 100644 --- a/examples/src/examples/misc/editor.example.mjs +++ b/examples/src/examples/misc/editor.example.mjs @@ -135,20 +135,9 @@ light.addComponent('light', { app.root.addChild(light); light.setEulerAngles(0, 0, -60); -// create layers -const gizmoLayer = new pc.Layer({ - name: 'Gizmo', - clearDepthBuffer: true, - opaqueSortMode: pc.SORTMODE_NONE, - transparentSortMode: pc.SORTMODE_NONE -}); -const layers = app.scene.layers; -layers.push(gizmoLayer); -camera.camera.layers = camera.camera.layers.concat(gizmoLayer.id); - // create gizmo let skipObserverFire = false; -const gizmoHandler = new GizmoHandler(app, camera.camera, gizmoLayer); +const gizmoHandler = new GizmoHandler(camera.camera); const setGizmoControls = () => { skipObserverFire = true; data.set('gizmo', { @@ -261,6 +250,7 @@ data.on('*:set', (/** @type {string} */ path, /** @type {any} */ value) => { }); // selector +const layers = app.scene.layers; const selector = new Selector(app, camera.camera, [layers.getLayerByName('World')]); selector.on('select', (/** @type {pc.GraphNode} */ node, /** @type {boolean} */ clear) => { if (gizmoHandler.hasPointer) { diff --git a/examples/src/examples/misc/editor.gizmo-handler.mjs b/examples/src/examples/misc/editor.gizmo-handler.mjs index 79f9a93fef4..173c05a0006 100644 --- a/examples/src/examples/misc/editor.gizmo-handler.mjs +++ b/examples/src/examples/misc/editor.gizmo-handler.mjs @@ -34,15 +34,14 @@ class GizmoHandler { _hasPointer = false; /** - * @param {pc.AppBase} app - The application. * @param {pc.CameraComponent} camera - The camera component. - * @param {pc.Layer} layer - The gizmo layer */ - constructor(app, camera, layer) { + constructor(camera) { + const layer = pc.Gizmo.createLayer(camera.system.app); this._gizmos = { - translate: new pc.TranslateGizmo(app, camera, layer), - rotate: new pc.RotateGizmo(app, camera, layer), - scale: new pc.ScaleGizmo(app, camera, layer) + translate: new pc.TranslateGizmo(camera, layer), + rotate: new pc.RotateGizmo(camera, layer), + scale: new pc.ScaleGizmo(camera, layer) }; for (const type in this._gizmos) { diff --git a/src/extras/gizmo/axis-shapes.js b/src/extras/gizmo/axis-shapes.js deleted file mode 100644 index 213299d7cb3..00000000000 --- a/src/extras/gizmo/axis-shapes.js +++ /dev/null @@ -1,742 +0,0 @@ -import { Color } from '../../core/math/color.js'; -import { Vec3 } from '../../core/math/vec3.js'; -import { Quat } from '../../core/math/quat.js'; -import { ShaderMaterial } from '../../scene/materials/shader-material.js'; -import { MeshInstance } from '../../scene/mesh-instance.js'; -import { Entity } from '../../framework/entity.js'; -import { CULLFACE_NONE, CULLFACE_BACK, SEMANTIC_POSITION, SEMANTIC_COLOR } from '../../platform/graphics/constants.js'; -import { BLEND_NORMAL } from '../../scene/constants.js'; - -import { COLOR_GRAY } from './color.js'; -import { TriData } from './tri-data.js'; -import { Mesh } from '../../scene/mesh.js'; -import { BoxGeometry } from '../../scene/geometry/box-geometry.js'; -import { CylinderGeometry } from '../../scene/geometry/cylinder-geometry.js'; -import { ConeGeometry } from '../../scene/geometry/cone-geometry.js'; -import { PlaneGeometry } from '../../scene/geometry/plane-geometry.js'; -import { SphereGeometry } from '../../scene/geometry/sphere-geometry.js'; -import { TorusGeometry } from '../../scene/geometry/torus-geometry.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); - -const GEOMETRIES = { - box: BoxGeometry, - cone: ConeGeometry, - cylinder: CylinderGeometry, - plane: PlaneGeometry, - sphere: SphereGeometry, - torus: TorusGeometry -}; - -const shaderDesc = { - uniqueName: 'axis-shape', - attributes: { - vertex_position: SEMANTIC_POSITION, - vertex_color: SEMANTIC_COLOR - }, - vertexCode: /* 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; - // store z/w for later use in fragment shader - vZW = gl_Position.zw; - // disable depth clipping - // gl_Position.z = 0.0; - } - `, - fragmentCode: /* glsl */` - precision highp float; - varying vec4 vColor; - varying vec2 vZW; - void main(void) { - gl_FragColor = vec4(gammaCorrectOutput(decodeGamma(vColor)), vColor.w); - // clamp depth in Z to [0, 1] range - gl_FragDepth = max(0.0, min(1.0, (vZW.x / vZW.y + 1.0) * 0.5)); - } - ` -}; - -// temporary variables -const tmpV1 = new Vec3(); -const tmpV2 = new Vec3(); -const tmpQ1 = new Quat(); - -function createShadowMesh(device, entity, type, color = Color.WHITE, templateOpts) { - const Geometry = GEOMETRIES[type]; - if (!Geometry) { - throw new Error('Invalid primitive type.'); - } - - const geom = new Geometry(templateOpts); - geom.colors = []; - - const wtm = entity.getWorldTransform().clone().invert(); - tmpV1.copy(LIGHT_DIR); - wtm.transformVector(tmpV1, tmpV1); - tmpV1.normalize(); - const numVertices = geom.positions.length / 3; - const shadow = calculateShadow(tmpV1, numVertices, geom.normals); - for (let i = 0; i < shadow.length; i++) { - geom.colors.push( - shadow[i] * color.r * 0xFF, - shadow[i] * color.g * 0xFF, - shadow[i] * color.b * 0xFF, - color.a * 0xFF - ); - } - - const shadowMesh = Mesh.fromGeometry(device, geom); - SHADOW_MESH_MAP.set(shadowMesh, shadow); - - return shadowMesh; -} - -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); - shadow.push(dot * SHADOW_DAMP_SCALE + SHADOW_DAMP_OFFSET); - } - - 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 { - _position; - - _rotation; - - _scale; - - _layers = []; - - _disabled; - - _defaultColor = Color.WHITE; - - _hoverColor = Color.BLACK; - - _disabledColor = COLOR_GRAY; - - _cull = CULLFACE_BACK; - - device; - - axis; - - entity; - - triData = []; - - meshInstances = []; - - constructor(device, options) { - this.device = device; - this.axis = options.axis ?? 'x'; - this._position = options.position ?? new Vec3(); - this._rotation = options.rotation ?? new Vec3(); - this._scale = options.scale ?? new Vec3(1, 1, 1); - - this._disabled = options.disabled ?? false; - - this._layers = options.layers ?? this._layers; - - if (options.defaultColor instanceof Color) { - this._defaultColor = options.defaultColor; - } - if (options.hoverColor instanceof Color) { - this._hoverColor = options.hoverColor; - } - if (options.disabledColor instanceof Color) { - this._disabledColor = options.disabledColor; - } - } - - set disabled(value) { - for (let i = 0; i < this.meshInstances.length; i++) { - setShadowMeshColor(this.meshInstances[i].mesh, value ? this._disabledColor : this._defaultColor); - } - this._disabled = value ?? false; - } - - get disabled() { - return this._disabled; - } - - _createRoot(name) { - this.entity = new Entity(`${name}:${this.axis}`); - this._updateRootTransform(); - } - - _updateRootTransform() { - this.entity.setLocalPosition(this._position); - this.entity.setLocalEulerAngles(this._rotation); - this.entity.setLocalScale(this._scale); - } - - _addRenderMeshes(entity, meshes) { - const material = new ShaderMaterial(shaderDesc); - 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], material); - meshInstances.push(mi); - this.meshInstances.push(mi); - } - entity.addComponent('render', { - meshInstances: meshInstances, - layers: this._layers, - castShadows: false - }); - } - - _addRenderShadowMesh(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; - } - - for (let i = 0; i < this.meshInstances.length; i++) { - const color = state ? this._hoverColor : this._defaultColor; - setShadowMeshColor(this.meshInstances[i].mesh, color); - } - } - - destroy() { - this.entity.destroy(); - } -} - -class AxisArrow extends AxisShape { - _gap = 0; - - _lineThickness = 0.02; - - _lineLength = 0.5; - - _arrowThickness = 0.12; - - _arrowLength = 0.18; - - _tolerance = 0.1; - - constructor(device, options = {}) { - super(device, options); - - this.triData = [ - new TriData(new ConeGeometry()), - new TriData(new CylinderGeometry(), 1) - ]; - - this._createArrow(); - } - - set gap(value) { - this._gap = value ?? 0; - this._updateHead(); - this._updateLine(); - } - - get gap() { - return this._gap; - } - - set lineThickness(value) { - this._lineThickness = value ?? 1; - this._updateHead(); - this._updateLine(); - } - - get lineThickness() { - return this._lineThickness; - } - - set lineLength(value) { - this._lineLength = value ?? 1; - this._updateHead(); - this._updateLine(); - } - - get lineLength() { - return this._lineLength; - } - - set arrowThickness(value) { - this._arrowThickness = value ?? 1; - this._updateHead(); - } - - get arrowThickness() { - return this._arrowThickness; - } - - set arrowLength(value) { - this._arrowLength = value ?? 1; - this._updateHead(); - } - - get arrowLength() { - return this._arrowLength; - } - - set tolerance(value) { - this._tolerance = value; - this._updateLine(); - } - - get tolerance() { - return this._tolerance; - } - - _createArrow() { - this._createRoot('arrow'); - - // head - this._head = new Entity(`head:${this.axis}`); - this.entity.addChild(this._head); - this._updateHead(); - this._addRenderShadowMesh(this._head, 'cone'); - - // line - this._line = new Entity(`line:${this.axis}`); - this.entity.addChild(this._line); - this._updateLine(); - this._addRenderShadowMesh(this._line, 'cylinder'); - } - - _updateHead() { - // intersect - tmpV1.set(0, this._gap + this._arrowLength * 0.5 + this._lineLength, 0); - tmpQ1.set(0, 0, 0, 1); - tmpV2.set(this._arrowThickness, this._arrowLength, this._arrowThickness); - this.triData[0].setTransform(tmpV1, tmpQ1, tmpV2); - - this._head.setLocalPosition(0, this._gap + this._arrowLength * 0.5 + this._lineLength, 0); - this._head.setLocalScale(this._arrowThickness, this._arrowLength, this._arrowThickness); - } - - _updateLine() { - // intersect - tmpV1.set(0, this._gap + this._lineLength * 0.5, 0); - tmpQ1.set(0, 0, 0, 1); - tmpV2.set(this._lineThickness + this._tolerance, this._lineLength, this._lineThickness + this._tolerance); - this.triData[1].setTransform(tmpV1, tmpQ1, tmpV2); - - // render - this._line.setLocalPosition(0, this._gap + this._lineLength * 0.5, 0); - this._line.setLocalScale(this._lineThickness, this._lineLength, this._lineThickness); - } -} - -class AxisBoxCenter extends AxisShape { - _size = 0.12; - - _tolerance = 0.05; - - constructor(device, options = {}) { - super(device, options); - - this.triData = [ - new TriData(new BoxGeometry(), 2) - ]; - - this._createCenter(); - } - - _createCenter() { - this._createRoot('boxCenter'); - this._updateTransform(); - - // box - this._addRenderShadowMesh(this.entity, 'box'); - } - - set size(value) { - this._size = value ?? 1; - this._updateTransform(); - } - - get size() { - return this._size; - } - - set tolerance(value) { - this._tolerance = value; - this._updateTransform(); - } - - get tolerance() { - return this._tolerance; - } - - _updateTransform() { - // intersect/render - this.entity.setLocalScale(this._size, this._size, this._size); - } -} - -class AxisBoxLine extends AxisShape { - _gap = 0; - - _lineThickness = 0.02; - - _lineLength = 0.5; - - _boxSize = 0.12; - - _tolerance = 0.1; - - constructor(device, options = {}) { - super(device, options); - - this.triData = [ - new TriData(new BoxGeometry()), - new TriData(new CylinderGeometry(), 1) - ]; - - this._createBoxLine(); - } - - set gap(value) { - this._gap = value ?? 0; - this._updateLine(); - this._updateBox(); - } - - get gap() { - return this._gap; - } - - set lineThickness(value) { - this._lineThickness = value ?? 1; - this._updateLine(); - this._updateBox(); - } - - get lineThickness() { - return this._lineThickness; - } - - set lineLength(value) { - this._lineLength = value ?? 1; - this._updateLine(); - this._updateBox(); - } - - get lineLength() { - return this._lineLength; - } - - set boxSize(value) { - this._boxSize = value ?? 1; - this._updateBox(); - } - - get boxSize() { - return this._boxSize; - } - - set tolerance(value) { - this._tolerance = value; - this._updateLine(); - } - - get tolerance() { - return this._tolerance; - } - - _createBoxLine() { - this._createRoot('boxLine'); - - // box - this._box = new Entity(`box:${this.axis}`); - this.entity.addChild(this._box); - this._updateBox(); - this._addRenderShadowMesh(this._box, 'box'); - - // line - this._line = new Entity(`line:${this.axis}`); - this.entity.addChild(this._line); - this._updateLine(); - this._addRenderShadowMesh(this._line, 'cylinder'); - - } - - _updateBox() { - // intersect - tmpV1.set(0, this._gap + this._boxSize * 0.5 + this._lineLength, 0); - tmpQ1.set(0, 0, 0, 1); - tmpV2.set(this._boxSize, this._boxSize, this._boxSize); - this.triData[0].setTransform(tmpV1, tmpQ1, tmpV2); - - // render - this._box.setLocalPosition(0, this._gap + this._boxSize * 0.5 + this._lineLength, 0); - this._box.setLocalScale(this._boxSize, this._boxSize, this._boxSize); - } - - _updateLine() { - // intersect - tmpV1.set(0, this._gap + this._lineLength * 0.5, 0); - tmpQ1.set(0, 0, 0, 1); - tmpV2.set(this._lineThickness + this._tolerance, this._lineLength, this._lineThickness + this._tolerance); - this.triData[1].setTransform(tmpV1, tmpQ1, tmpV2); - - // render - this._line.setLocalPosition(0, this._gap + this._lineLength * 0.5, 0); - this._line.setLocalScale(this._lineThickness, this._lineLength, this._lineThickness); - } -} - -class AxisDisk extends AxisShape { - _tubeRadius = 0.01; - - _ringRadius = 0.5; - - _sectorAngle; - - _lightDir; - - _tolerance = 0.05; - - constructor(device, options = {}) { - super(device, options); - - this._tubeRadius = options.tubeRadius ?? this._tubeRadius; - this._ringRadius = options.ringRadius ?? this._ringRadius; - this._sectorAngle = options.sectorAngle ?? this._sectorAngle; - - this.triData = [ - new TriData(this._createTorusGeometry()) - ]; - - this._createDisk(); - } - - _createTorusGeometry() { - return new TorusGeometry({ - tubeRadius: this._tubeRadius + this._tolerance, - ringRadius: this._ringRadius, - sectorAngle: this._sectorAngle, - segments: TORUS_INTERSECT_SEGMENTS - }); - } - - _createTorusMesh(sectorAngle) { - const color = this._disabled ? this._disabledColor : this._defaultColor; - return createShadowMesh(this.device, this.entity, 'torus', color, { - tubeRadius: this._tubeRadius, - ringRadius: this._ringRadius, - sectorAngle: sectorAngle, - segments: TORUS_RENDER_SEGMENTS - }); - } - - _createDisk() { - this._createRoot('disk'); - - // arc/circle - this._addRenderMeshes(this.entity, [ - this._createTorusMesh(this._sectorAngle), - this._createTorusMesh(360) - ]); - this.drag(false); - } - - set tubeRadius(value) { - this._tubeRadius = value ?? 0.1; - this._updateTransform(); - } - - get tubeRadius() { - return this._tubeRadius; - } - - set ringRadius(value) { - this._ringRadius = value ?? 0.1; - this._updateTransform(); - } - - get ringRadius() { - return this._ringRadius; - } - - set tolerance(value) { - this._tolerance = value; - this._updateTransform(); - } - - get tolerance() { - return this._tolerance; - } - - _updateTransform() { - // intersect - this.triData[0].fromGeometry(this._createTorusGeometry()); - - // render - this.meshInstances[0].mesh = this._createTorusMesh(this._sectorAngle); - this.meshInstances[1].mesh = this._createTorusMesh(360); - } - - drag(state) { - this.meshInstances[0].visible = !state; - this.meshInstances[1].visible = state; - } - - hide(state) { - if (state) { - this.meshInstances[0].visible = false; - this.meshInstances[1].visible = false; - return; - } - - this.drag(false); - } -} - -class AxisPlane extends AxisShape { - _cull = CULLFACE_NONE; - - _size = 0.2; - - _gap = 0.1; - - constructor(device, options = {}) { - super(device, options); - - this.triData = [ - new TriData(new PlaneGeometry()) - ]; - - this._createPlane(); - } - - _getPosition() { - const offset = this._size / 2 + this._gap; - const position = new Vec3(offset, offset, offset); - position[this.axis] = 0; - return position; - } - - _createPlane() { - this._createRoot('plane'); - this._updateTransform(); - - // plane - this._addRenderShadowMesh(this.entity, 'plane'); - } - - set size(value) { - this._size = value ?? 1; - this._updateTransform(); - } - - get size() { - return this._size; - } - - set gap(value) { - this._gap = value ?? 0; - this._updateTransform(); - } - - get gap() { - return this._gap; - } - - _updateTransform() { - // intersect/render - this.entity.setLocalPosition(this._getPosition()); - this.entity.setLocalEulerAngles(this._rotation); - this.entity.setLocalScale(this._size, this._size, this._size); - } -} - -class AxisSphereCenter extends AxisShape { - _size = 0.12; - - _tolerance = 0.05; - - constructor(device, options = {}) { - super(device, options); - - this.triData = [ - new TriData(new SphereGeometry(), 2) - ]; - - this._createCenter(); - } - - _createCenter() { - this._createRoot('sphereCenter'); - this._updateTransform(); - - // box - this._addRenderShadowMesh(this.entity, 'sphere'); - } - - set size(value) { - this._size = value ?? 1; - this._updateTransform(); - } - - get size() { - return this._size; - } - - set tolerance(value) { - this._tolerance = value; - this._updateTransform(); - } - - get tolerance() { - return this._tolerance; - } - - _updateTransform() { - // intersect/render - this.entity.setLocalScale(this._size, this._size, this._size); - } -} - -export { AxisShape, AxisArrow, AxisBoxCenter, AxisBoxLine, AxisDisk, AxisPlane, AxisSphereCenter }; diff --git a/src/extras/gizmo/gizmo.js b/src/extras/gizmo/gizmo.js index aa65cf53976..606135a2734 100644 --- a/src/extras/gizmo/gizmo.js +++ b/src/extras/gizmo/gizmo.js @@ -1,19 +1,20 @@ +import { Debug } from '../../core/debug.js'; import { math } from '../../core/math/math.js'; import { Vec3 } from '../../core/math/vec3.js'; import { Mat4 } from '../../core/math/mat4.js'; import { Ray } from '../../core/shape/ray.js'; import { EventHandler } from '../../core/event-handler.js'; -import { PROJECTION_PERSPECTIVE } from '../../scene/constants.js'; +import { CameraComponent } from '../../framework/components/camera/component.js'; +import { PROJECTION_PERSPECTIVE, SORTMODE_NONE } from '../../scene/constants.js'; import { Entity } from '../../framework/entity.js'; +import { Layer } from '../../scene/layer.js'; import { GIZMOSPACE_LOCAL, GIZMOSPACE_WORLD } from './constants.js'; /** * @import { AppBase } from '../../framework/app-base.js' - * @import { CameraComponent } from '../../framework/components/camera/component.js' * @import { GraphNode } from '../../scene/graph-node.js' * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' - * @import { Layer } from '../../scene/layer.js' * @import { MeshInstance } from '../../scene/mesh-instance.js' * @import { TriData } from './tri-data.js' */ @@ -25,9 +26,11 @@ const tmpM2 = new Mat4(); const tmpR1 = new Ray(); // constants -const MIN_GIZMO_SCALE = 1e-4; +const LAYER_NAME = 'Gizmo'; +const MIN_SCALE = 1e-4; const PERS_SCALE_RATIO = 0.3; const ORTHO_SCALE_RATIO = 0.32; +const UPDATE_EPSILON = 1e-6; /** * The base class for all gizmos. @@ -227,22 +230,44 @@ class Gizmo extends EventHandler { */ intersectData = []; + /** + * Creates a new gizmo layer and adds it to the scene. + * + * @param {AppBase} app - The app. + * @param {string} [layerName] - The layer name. Defaults to 'Gizmo'. + * @param {number} [layerIndex] - The layer index. Defaults to the end of the layer list. + * @returns {Layer} The new layer. + */ + static createLayer(app, layerName = LAYER_NAME, layerIndex) { + const layer = new Layer({ + name: layerName, + clearDepthBuffer: true, + opaqueSortMode: SORTMODE_NONE, + transparentSortMode: SORTMODE_NONE + }); + app.scene.layers.insert(layer, layerIndex ?? app.scene.layers.layerList.length); + return layer; + } + /** * Creates a new Gizmo object. * - * @param {AppBase} app - The application instance. * @param {CameraComponent} camera - The camera component. - * @param {Layer} layer - The render layer. - * @example + * @param {Layer} layer - The render layer. This can be provided by the user or will be created + * and added to the scene and camera if not provided. Successive gizmos will share the same layer + * and will be removed from the camera and scene when the last gizmo is destroyed. * const gizmo = new pc.Gizmo(app, camera, layer); */ - constructor(app, camera, layer) { + constructor(camera, layer) { + Debug.assert(camera instanceof CameraComponent, 'Incorrect parameters for Gizmos\'s constructor. Use new Gizmo(camera, layer)'); super(); - this._app = app; - this._device = app.graphicsDevice; this._camera = camera; + this._app = camera.system.app; + this._device = this._app.graphicsDevice; + this._layer = layer; + camera.layers = camera.layers.concat(layer.id); this.root = new Entity('gizmo'); this._app.root.addChild(this.root); @@ -258,9 +283,22 @@ class Gizmo extends EventHandler { this._device.canvas.addEventListener('pointermove', this._onPointerMove, true); this._device.canvas.addEventListener('pointerup', this._onPointerUp); - app.on('update', () => this._updateScale()); + this._app.on('update', () => { + this._updatePosition(); + this._updateRotation(); + this._updateScale(); + }); + + this._app.on('destroy', () => this.destroy()); + } - app.on('destroy', () => this.destroy()); + /** + * Sets the gizmo render layer. + * + * @type {Layer} + */ + get layer() { + return this._layer; } /** @@ -358,8 +396,12 @@ class Gizmo extends EventHandler { tmpV1.add(node.getPosition()); } tmpV1.mulScalar(1.0 / (this.nodes.length || 1)); - this.root.setPosition(tmpV1); + if (tmpV1.distance(this.root.getPosition()) < UPDATE_EPSILON) { + return; + } + + this.root.setPosition(tmpV1); this.fire(Gizmo.EVENT_POSITIONUPDATE, tmpV1); } @@ -371,8 +413,12 @@ class Gizmo extends EventHandler { if (this._coordSpace === GIZMOSPACE_LOCAL && this.nodes.length !== 0) { tmpV1.copy(this.nodes[this.nodes.length - 1].getEulerAngles()); } - this.root.setEulerAngles(tmpV1); + if (tmpV1.distance(this.root.getEulerAngles()) < UPDATE_EPSILON) { + return; + } + + this.root.setEulerAngles(tmpV1); this.fire(Gizmo.EVENT_ROTATIONUPDATE, tmpV1); } @@ -388,10 +434,13 @@ class Gizmo extends EventHandler { } else { this._scale = this._camera.orthoHeight * ORTHO_SCALE_RATIO; } - this._scale = Math.max(this._scale * this._size, MIN_GIZMO_SCALE); + this._scale = Math.max(this._scale * this._size, MIN_SCALE); - this.root.setLocalScale(this._scale, this._scale, this._scale); + if (Math.abs(this._scale - this.root.getLocalScale().x) < UPDATE_EPSILON) { + return; + } + this.root.setLocalScale(this._scale, this._scale, this._scale); this.fire(Gizmo.EVENT_SCALEUPDATE, this._scale); } @@ -450,19 +499,23 @@ class Gizmo extends EventHandler { /** * Attach an array of graph nodes to the gizmo. * - * @param {GraphNode[]} [nodes] - The graph nodes. Defaults to []. + * @param {GraphNode[] | GraphNode} [nodes] - The graph nodes. Defaults to []. * @example * const gizmo = new pc.Gizmo(app, camera, layer); * gizmo.attach([boxA, boxB]); */ attach(nodes = []) { - if (nodes.length === 0) { - return; + if (Array.isArray(nodes)) { + if (nodes.length === 0) { + return; + } + this.nodes = nodes; + } else { + this.nodes = [nodes]; } - - this.nodes = nodes; this._updatePosition(); this._updateRotation(); + this._updateScale(); this.fire(Gizmo.EVENT_NODESATTACH); diff --git a/src/extras/gizmo/rotate-gizmo.js b/src/extras/gizmo/rotate-gizmo.js index 8e0522a6bc9..bbd4c6771b7 100644 --- a/src/extras/gizmo/rotate-gizmo.js +++ b/src/extras/gizmo/rotate-gizmo.js @@ -5,12 +5,11 @@ import { Mat4 } from '../../core/math/mat4.js'; import { Vec3 } from '../../core/math/vec3.js'; import { PROJECTION_ORTHOGRAPHIC, PROJECTION_PERSPECTIVE } from '../../scene/constants.js'; -import { AxisDisk } from './axis-shapes.js'; +import { ArcShape } from './shape/arc-shape.js'; import { GIZMOSPACE_LOCAL, GIZMOAXIS_FACE, GIZMOAXIS_X, GIZMOAXIS_Y, GIZMOAXIS_Z } from './constants.js'; import { TransformGizmo } from './transform-gizmo.js'; /** - * @import { AppBase } from '../../framework/app-base.js' * @import { CameraComponent } from '../../framework/components/camera/component.js' * @import { GraphNode } from '../../scene/graph-node.js' * @import { Layer } from '../../scene/layer.js' @@ -35,33 +34,37 @@ const GUIDE_ANGLE_COLOR = new Color(0, 0, 0, 0.3); */ class RotateGizmo extends TransformGizmo { _shapes = { - z: new AxisDisk(this._device, { + z: new ArcShape(this._device, { axis: GIZMOAXIS_Z, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(90, 0, 90), defaultColor: this._meshColors.axis.z, hoverColor: this._meshColors.hover.z, sectorAngle: 180 }), - x: new AxisDisk(this._device, { + x: new ArcShape(this._device, { axis: GIZMOAXIS_X, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, -90), defaultColor: this._meshColors.axis.x, hoverColor: this._meshColors.hover.x, sectorAngle: 180 }), - y: new AxisDisk(this._device, { + y: new ArcShape(this._device, { axis: GIZMOAXIS_Y, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, 0), defaultColor: this._meshColors.axis.y, hoverColor: this._meshColors.hover.y, sectorAngle: 180 }), - face: new AxisDisk(this._device, { + face: new ArcShape(this._device, { axis: GIZMOAXIS_FACE, layers: [this._layer.id], + shading: this._shading, rotation: this._getLookAtEulerAngles(this._camera.entity.getPosition()), defaultColor: this._meshColors.axis.f, hoverColor: this._meshColors.hover.f, @@ -125,14 +128,13 @@ class RotateGizmo extends TransformGizmo { /** * Creates a new RotateGizmo object. * - * @param {AppBase} app - The application instance. * @param {CameraComponent} camera - The camera component. * @param {Layer} layer - The render layer. * @example * const gizmo = new pc.RotateGizmo(app, camera, layer); */ - constructor(app, camera, layer) { - super(app, camera, layer); + constructor(camera, layer) { + super(camera, layer); this._createTransform(); @@ -167,7 +169,7 @@ class RotateGizmo extends TransformGizmo { this._nodeOffsets.clear(); }); - app.on('update', () => { + this._app.on('update', () => { this._faceAxisLookAtCamera(); this._xyzAxisLookAtCamera(); diff --git a/src/extras/gizmo/scale-gizmo.js b/src/extras/gizmo/scale-gizmo.js index 572acb326cf..f782cc988b5 100644 --- a/src/extras/gizmo/scale-gizmo.js +++ b/src/extras/gizmo/scale-gizmo.js @@ -1,12 +1,13 @@ import { Vec3 } from '../../core/math/vec3.js'; import { Quat } from '../../core/math/quat.js'; -import { AxisBoxCenter, AxisBoxLine, AxisPlane } from './axis-shapes.js'; import { GIZMOSPACE_LOCAL, GIZMOAXIS_X, GIZMOAXIS_XYZ, GIZMOAXIS_Y, GIZMOAXIS_Z } from './constants.js'; import { TransformGizmo } from './transform-gizmo.js'; +import { BoxShape } from './shape/box-shape.js'; +import { PlaneShape } from './shape/plane-shape.js'; +import { BoxLineShape } from './shape/boxline-shape.js'; /** - * @import { AppBase } from '../../framework/app-base.js' * @import { CameraComponent } from '../../framework/components/camera/component.js' * @import { GraphNode } from '../../scene/graph-node.js' * @import { Layer } from '../../scene/layer.js' @@ -24,53 +25,57 @@ const tmpQ1 = new Quat(); */ class ScaleGizmo extends TransformGizmo { _shapes = { - xyz: new AxisBoxCenter(this._device, { + xyz: new BoxShape(this._device, { axis: GIZMOAXIS_XYZ, layers: [this._layer.id], + shading: this._shading, defaultColor: this._meshColors.axis.xyz, hoverColor: this._meshColors.hover.xyz }), - yz: new AxisPlane(this._device, { + yz: new PlaneShape(this._device, { axis: GIZMOAXIS_X, - flipAxis: GIZMOAXIS_Y, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, -90), defaultColor: this._meshColors.axis.x, hoverColor: this._meshColors.hover.x }), - xz: new AxisPlane(this._device, { + xz: new PlaneShape(this._device, { axis: GIZMOAXIS_Y, - flipAxis: GIZMOAXIS_Z, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, 0), defaultColor: this._meshColors.axis.y, hoverColor: this._meshColors.hover.y }), - xy: new AxisPlane(this._device, { + xy: new PlaneShape(this._device, { axis: GIZMOAXIS_Z, - flipAxis: GIZMOAXIS_X, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(90, 0, 0), defaultColor: this._meshColors.axis.z, hoverColor: this._meshColors.hover.z }), - x: new AxisBoxLine(this._device, { + x: new BoxLineShape(this._device, { axis: GIZMOAXIS_X, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, -90), defaultColor: this._meshColors.axis.x, hoverColor: this._meshColors.hover.x }), - y: new AxisBoxLine(this._device, { + y: new BoxLineShape(this._device, { axis: GIZMOAXIS_Y, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, 0), defaultColor: this._meshColors.axis.y, hoverColor: this._meshColors.hover.y }), - z: new AxisBoxLine(this._device, { + z: new BoxLineShape(this._device, { axis: GIZMOAXIS_Z, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(90, 0, 0), defaultColor: this._meshColors.axis.z, hoverColor: this._meshColors.hover.z @@ -103,14 +108,13 @@ class ScaleGizmo extends TransformGizmo { /** * Creates a new ScaleGizmo object. * - * @param {AppBase} app - The application instance. * @param {CameraComponent} camera - The camera component. * @param {Layer} layer - The render layer. * @example * const gizmo = new pc.ScaleGizmo(app, camera, layer); */ - constructor(app, camera, layer) { - super(app, camera, layer); + constructor(camera, layer) { + super(camera, layer); this._createTransform(); diff --git a/src/extras/gizmo/shape/arc-shape.js b/src/extras/gizmo/shape/arc-shape.js new file mode 100644 index 00000000000..273fc42f2ce --- /dev/null +++ b/src/extras/gizmo/shape/arc-shape.js @@ -0,0 +1,115 @@ +import { TorusGeometry } from '../../../scene/geometry/torus-geometry.js'; +import { TriData } from '../tri-data.js'; +import { Shape } from './shape.js'; + +const TORUS_RENDER_SEGMENTS = 80; +const TORUS_INTERSECT_SEGMENTS = 20; + +class ArcShape extends Shape { + _tubeRadius = 0.01; + + _ringRadius = 0.5; + + _sectorAngle; + + _lightDir; + + _tolerance = 0.05; + + constructor(device, options = {}) { + super(device, options); + + this._tubeRadius = options.tubeRadius ?? this._tubeRadius; + this._ringRadius = options.ringRadius ?? this._ringRadius; + this._sectorAngle = options.sectorAngle ?? this._sectorAngle; + + this.triData = [ + new TriData(this._createTorusGeometry()) + ]; + + this._createDisk(); + } + + _createTorusGeometry() { + return new TorusGeometry({ + tubeRadius: this._tubeRadius + this._tolerance, + ringRadius: this._ringRadius, + sectorAngle: this._sectorAngle, + segments: TORUS_INTERSECT_SEGMENTS + }); + } + + _createTorusMesh(sectorAngle) { + const geom = new TorusGeometry({ + tubeRadius: this._tubeRadius, + ringRadius: this._ringRadius, + sectorAngle: sectorAngle, + segments: TORUS_RENDER_SEGMENTS + }); + return this._createMesh(geom, this._shading); + } + + _createDisk() { + this._createRoot('disk'); + + // arc/circle + this._createRenderComponent(this.entity, [ + this._createTorusMesh(this._sectorAngle), + this._createTorusMesh(360) + ]); + this.drag(false); + } + + set tubeRadius(value) { + this._tubeRadius = value ?? 0.1; + this._updateTransform(); + } + + get tubeRadius() { + return this._tubeRadius; + } + + set ringRadius(value) { + this._ringRadius = value ?? 0.1; + this._updateTransform(); + } + + get ringRadius() { + return this._ringRadius; + } + + set tolerance(value) { + this._tolerance = value; + this._updateTransform(); + } + + get tolerance() { + return this._tolerance; + } + + _updateTransform() { + // intersect + this.triData[0].fromGeometry(this._createTorusGeometry()); + + // render + this.meshInstances[0].mesh = this._createTorusMesh(this._sectorAngle); + this.meshInstances[1].mesh = this._createTorusMesh(360); + } + + drag(state) { + this.meshInstances[0].visible = !state; + this.meshInstances[1].visible = state; + } + + hide(state) { + if (state) { + this.meshInstances[0].visible = false; + this.meshInstances[1].visible = false; + return; + } + + this.drag(false); + } +} + +export { ArcShape }; diff --git a/src/extras/gizmo/shape/arrow-shape.js b/src/extras/gizmo/shape/arrow-shape.js new file mode 100644 index 00000000000..78064e102cd --- /dev/null +++ b/src/extras/gizmo/shape/arrow-shape.js @@ -0,0 +1,138 @@ +import { Quat } from '../../../core/math/quat.js'; +import { Vec3 } from '../../../core/math/vec3.js'; +import { Entity } from '../../../framework/entity.js'; +import { ConeGeometry } from '../../../scene/geometry/cone-geometry.js'; +import { CylinderGeometry } from '../../../scene/geometry/cylinder-geometry.js'; +import { TriData } from '../tri-data.js'; +import { Shape } from './shape.js'; + +const tmpV1 = new Vec3(); +const tmpV2 = new Vec3(); +const tmpQ1 = new Quat(); + +class ArrowShape extends Shape { + _gap = 0; + + _lineThickness = 0.02; + + _lineLength = 0.5; + + _arrowThickness = 0.12; + + _arrowLength = 0.18; + + _tolerance = 0.1; + + _head; + + _line; + + constructor(device, options = {}) { + super(device, options); + + this.triData = [ + new TriData(new ConeGeometry()), + new TriData(new CylinderGeometry(), 1) + ]; + + this._createArrow(); + } + + set gap(value) { + this._gap = value ?? 0; + this._updateHead(); + this._updateLine(); + } + + get gap() { + return this._gap; + } + + set lineThickness(value) { + this._lineThickness = value ?? 1; + this._updateHead(); + this._updateLine(); + } + + get lineThickness() { + return this._lineThickness; + } + + set lineLength(value) { + this._lineLength = value ?? 1; + this._updateHead(); + this._updateLine(); + } + + get lineLength() { + return this._lineLength; + } + + set arrowThickness(value) { + this._arrowThickness = value ?? 1; + this._updateHead(); + } + + get arrowThickness() { + return this._arrowThickness; + } + + set arrowLength(value) { + this._arrowLength = value ?? 1; + this._updateHead(); + } + + get arrowLength() { + return this._arrowLength; + } + + set tolerance(value) { + this._tolerance = value; + this._updateLine(); + } + + get tolerance() { + return this._tolerance; + } + + _createArrow() { + this._createRoot('arrow'); + + // head + this._head = new Entity(`head:${this.axis}`); + this.entity.addChild(this._head); + this._updateHead(); + this._addRenderMesh(this._head, 'cone', this._shading); + + // line + this._line = new Entity(`line:${this.axis}`); + this.entity.addChild(this._line); + this._updateLine(); + this._addRenderMesh(this._line, 'cylinder', this._shading); + } + + _updateHead() { + // intersect + tmpV1.set(0, this._gap + this._arrowLength * 0.5 + this._lineLength, 0); + tmpQ1.set(0, 0, 0, 1); + tmpV2.set(this._arrowThickness, this._arrowLength, this._arrowThickness); + this.triData[0].setTransform(tmpV1, tmpQ1, tmpV2); + + this._head.setLocalPosition(0, this._gap + this._arrowLength * 0.5 + this._lineLength, 0); + this._head.setLocalScale(this._arrowThickness, this._arrowLength, this._arrowThickness); + } + + _updateLine() { + // intersect + tmpV1.set(0, this._gap + this._lineLength * 0.5, 0); + tmpQ1.set(0, 0, 0, 1); + tmpV2.set(this._lineThickness + this._tolerance, this._lineLength, this._lineThickness + this._tolerance); + this.triData[1].setTransform(tmpV1, tmpQ1, tmpV2); + + // render + this._line.setLocalPosition(0, this._gap + this._lineLength * 0.5, 0); + this._line.setLocalScale(this._lineThickness, this._lineLength, this._lineThickness); + } +} + +export { ArrowShape }; diff --git a/src/extras/gizmo/shape/box-shape.js b/src/extras/gizmo/shape/box-shape.js new file mode 100644 index 00000000000..5ec923247cc --- /dev/null +++ b/src/extras/gizmo/shape/box-shape.js @@ -0,0 +1,52 @@ +import { BoxGeometry } from '../../../scene/geometry/box-geometry.js'; +import { TriData } from '../tri-data.js'; +import { Shape } from './shape.js'; + +class BoxShape extends Shape { + _size = 0.12; + + _tolerance = 0.05; + + constructor(device, options = {}) { + super(device, options); + + this.triData = [ + new TriData(new BoxGeometry(), 2) + ]; + + this._createCenter(); + } + + _createCenter() { + this._createRoot('boxCenter'); + this._updateTransform(); + + // box + this._addRenderMesh(this.entity, 'box', this._shading); + } + + set size(value) { + this._size = value ?? 1; + this._updateTransform(); + } + + get size() { + return this._size; + } + + set tolerance(value) { + this._tolerance = value; + this._updateTransform(); + } + + get tolerance() { + return this._tolerance; + } + + _updateTransform() { + // intersect/render + this.entity.setLocalScale(this._size, this._size, this._size); + } +} + +export { BoxShape }; diff --git a/src/extras/gizmo/shape/boxline-shape.js b/src/extras/gizmo/shape/boxline-shape.js new file mode 100644 index 00000000000..14ef087f71d --- /dev/null +++ b/src/extras/gizmo/shape/boxline-shape.js @@ -0,0 +1,129 @@ +import { Quat } from '../../../core/math/quat.js'; +import { Vec3 } from '../../../core/math/vec3.js'; +import { Entity } from '../../../framework/entity.js'; +import { BoxGeometry } from '../../../scene/geometry/box-geometry.js'; +import { CylinderGeometry } from '../../../scene/geometry/cylinder-geometry.js'; +import { TriData } from '../tri-data.js'; +import { Shape } from './shape.js'; + +const tmpV1 = new Vec3(); +const tmpV2 = new Vec3(); +const tmpQ1 = new Quat(); + +class BoxLineShape extends Shape { + _gap = 0; + + _lineThickness = 0.02; + + _lineLength = 0.5; + + _boxSize = 0.12; + + _tolerance = 0.1; + + _box; + + _line; + + constructor(device, options = {}) { + super(device, options); + + this.triData = [ + new TriData(new BoxGeometry()), + new TriData(new CylinderGeometry(), 1) + ]; + + this._createBoxLine(); + } + + set gap(value) { + this._gap = value ?? 0; + this._updateLine(); + this._updateBox(); + } + + get gap() { + return this._gap; + } + + set lineThickness(value) { + this._lineThickness = value ?? 1; + this._updateLine(); + this._updateBox(); + } + + get lineThickness() { + return this._lineThickness; + } + + set lineLength(value) { + this._lineLength = value ?? 1; + this._updateLine(); + this._updateBox(); + } + + get lineLength() { + return this._lineLength; + } + + set boxSize(value) { + this._boxSize = value ?? 1; + this._updateBox(); + } + + get boxSize() { + return this._boxSize; + } + + set tolerance(value) { + this._tolerance = value; + this._updateLine(); + } + + get tolerance() { + return this._tolerance; + } + + _createBoxLine() { + this._createRoot('boxLine'); + + // box + this._box = new Entity(`box:${this.axis}`); + this.entity.addChild(this._box); + this._updateBox(); + this._addRenderMesh(this._box, 'box', this._shading); + + // line + this._line = new Entity(`line:${this.axis}`); + this.entity.addChild(this._line); + this._updateLine(); + this._addRenderMesh(this._line, 'cylinder', this._shading); + + } + + _updateBox() { + // intersect + tmpV1.set(0, this._gap + this._boxSize * 0.5 + this._lineLength, 0); + tmpQ1.set(0, 0, 0, 1); + tmpV2.set(this._boxSize, this._boxSize, this._boxSize); + this.triData[0].setTransform(tmpV1, tmpQ1, tmpV2); + + // render + this._box.setLocalPosition(0, this._gap + this._boxSize * 0.5 + this._lineLength, 0); + this._box.setLocalScale(this._boxSize, this._boxSize, this._boxSize); + } + + _updateLine() { + // intersect + tmpV1.set(0, this._gap + this._lineLength * 0.5, 0); + tmpQ1.set(0, 0, 0, 1); + tmpV2.set(this._lineThickness + this._tolerance, this._lineLength, this._lineThickness + this._tolerance); + this.triData[1].setTransform(tmpV1, tmpQ1, tmpV2); + + // render + this._line.setLocalPosition(0, this._gap + this._lineLength * 0.5, 0); + this._line.setLocalScale(this._lineThickness, this._lineLength, this._lineThickness); + } +} + +export { BoxLineShape }; diff --git a/src/extras/gizmo/shape/plane-shape.js b/src/extras/gizmo/shape/plane-shape.js new file mode 100644 index 00000000000..0cb81ece2cb --- /dev/null +++ b/src/extras/gizmo/shape/plane-shape.js @@ -0,0 +1,65 @@ +import { Vec3 } from '../../../core/math/vec3.js'; +import { CULLFACE_NONE } from '../../../platform/graphics/constants.js'; +import { PlaneGeometry } from '../../../scene/geometry/plane-geometry.js'; +import { TriData } from '../tri-data.js'; +import { Shape } from './shape.js'; + +class PlaneShape extends Shape { + _cull = CULLFACE_NONE; + + _size = 0.2; + + _gap = 0.1; + + constructor(device, options = {}) { + super(device, options); + + this.triData = [ + new TriData(new PlaneGeometry()) + ]; + + this._createPlane(); + } + + _getPosition() { + const offset = this._size / 2 + this._gap; + const position = new Vec3(offset, offset, offset); + position[this.axis] = 0; + return position; + } + + _createPlane() { + this._createRoot('plane'); + this._updateTransform(); + + // plane + this._addRenderMesh(this.entity, 'plane', this._shading); + } + + set size(value) { + this._size = value ?? 1; + this._updateTransform(); + } + + get size() { + return this._size; + } + + set gap(value) { + this._gap = value ?? 0; + this._updateTransform(); + } + + get gap() { + return this._gap; + } + + _updateTransform() { + // intersect/render + this.entity.setLocalPosition(this._getPosition()); + this.entity.setLocalEulerAngles(this._rotation); + this.entity.setLocalScale(this._size, this._size, this._size); + } +} + +export { PlaneShape }; diff --git a/src/extras/gizmo/shape/shape.js b/src/extras/gizmo/shape/shape.js new file mode 100644 index 00000000000..7d58ca97e10 --- /dev/null +++ b/src/extras/gizmo/shape/shape.js @@ -0,0 +1,349 @@ +import { Color } from '../../../core/math/color.js'; +import { Vec3 } from '../../../core/math/vec3.js'; +import { ShaderMaterial } from '../../../scene/materials/shader-material.js'; +import { MeshInstance } from '../../../scene/mesh-instance.js'; +import { Entity } from '../../../framework/entity.js'; +import { CULLFACE_BACK, SEMANTIC_POSITION, SEMANTIC_COLOR } from '../../../platform/graphics/constants.js'; +import { BLEND_NORMAL } from '../../../scene/constants.js'; + +import { COLOR_GRAY } from '../color.js'; +import { Mesh } from '../../../scene/mesh.js'; +import { Geometry } from '../../../scene/geometry/geometry.js'; +import { BoxGeometry } from '../../../scene/geometry/box-geometry.js'; +import { CylinderGeometry } from '../../../scene/geometry/cylinder-geometry.js'; +import { ConeGeometry } from '../../../scene/geometry/cone-geometry.js'; +import { PlaneGeometry } from '../../../scene/geometry/plane-geometry.js'; +import { SphereGeometry } from '../../../scene/geometry/sphere-geometry.js'; +import { TorusGeometry } from '../../../scene/geometry/torus-geometry.js'; +import { Mat4 } from '../../../core/math/mat4.js'; + +/** + * @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js'; + * @import { TriData } from '../tri-data.js'; + */ + +// constants +const SHADING_DAMP_SCALE = 0.25; +const SHADING_DAMP_OFFSET = 0.75; + +const LIGHT_DIR = new Vec3(1, 2, 3); + +const GEOMETRIES = { + box: BoxGeometry, + cone: ConeGeometry, + cylinder: CylinderGeometry, + plane: PlaneGeometry, + sphere: SphereGeometry, + torus: TorusGeometry +}; + +const shaderDesc = { + uniqueName: 'axis-shape', + attributes: { + vertex_position: SEMANTIC_POSITION, + vertex_color: SEMANTIC_COLOR + }, + vertexCode: /* 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; + } + `, + fragmentCode: /* glsl */` + precision highp float; + varying vec4 vColor; + varying vec2 vZW; + void main(void) { + gl_FragColor = vec4(gammaCorrectOutput(decodeGamma(vColor)), vColor.w); + gl_FragDepth = gl_FragCoord.z; + } + ` +}; + +const shadingMeshMap = new Map(); + +const tmpV1 = new Vec3(); +const tmpV2 = new Vec3(); +const tmpM1 = new Mat4(); +const tmpG = new Geometry(); +tmpG.positions = []; +tmpG.normals = []; + +/** + * Apply shadow to a geometry. + * + * @param {Geometry} geom - The geometry to apply shadow to. + * @param {Color} color - The color of the geometry. + * @param {Mat4} [transform] - The transform of the geometry. + * @returns {number[]} The shadow data. + */ +const applyShadowColor = (geom, color, transform) => { + if (!geom.normals || !geom.positions) { + return []; + } + + // transform light direction to local space + let localLightDir; + if (transform) { + localLightDir = tmpM1.copy(transform).invert() + .transformVector(tmpV1.copy(LIGHT_DIR), tmpV1) + .normalize(); + } + + // calculate shading intensity and apply to color + geom.colors = []; + const shading = []; + const numVertices = geom.positions.length / 3; + for (let i = 0; i < numVertices; i++) { + let strength = 1; + if (localLightDir) { + const x = geom.normals[i * 3]; + const y = geom.normals[i * 3 + 1]; + const z = geom.normals[i * 3 + 2]; + const normal = tmpV2.set(x, y, z); + const dot = localLightDir.dot(normal); + strength = dot * SHADING_DAMP_SCALE + SHADING_DAMP_OFFSET; + } + shading.push(strength); + geom.colors.push( + strength * color.r * 0xFF, + strength * color.g * 0xFF, + strength * color.b * 0xFF, + color.a * 0xFF + ); + } + + return shading; +}; + +/** + * Set the color of a mesh. + * + * @param {Mesh} mesh - The mesh to set the color of. + * @param {Color} color - The color to set the mesh to. + */ +const setMeshColor = (mesh, color) => { + const shading = shadingMeshMap.get(mesh); + const colors = []; + for (let i = 0; i < shading.length; i++) { + colors.push( + shading[i] * color.r * 0xFF, + shading[i] * color.g * 0xFF, + shading[i] * color.b * 0xFF, + color.a * 0xFF + ); + } + mesh.setColors32(colors); + mesh.update(); +}; + +class Shape { + _position; + + _rotation; + + _scale; + + _layers = []; + + _shading = true; + + _disabled; + + _defaultColor = Color.WHITE; + + _hoverColor = Color.BLACK; + + _disabledColor = COLOR_GRAY; + + _cull = CULLFACE_BACK; + + /** + * The graphics device. + * + * @type {GraphicsDevice} + */ + device; + + /** + * The axis of the shape. + * + * @type {string} + */ + axis; + + /** + * The entity of the shape. + * + * @type {Entity} + */ + entity; + + + /** + * The triangle data of the shape. + * + * @type {TriData[]} + */ + triData = []; + + /** + * The mesh instances of the shape. + * + * @type {MeshInstance[]} + */ + meshInstances = []; + + constructor(device, options) { + this.device = device; + this.axis = options.axis ?? 'x'; + this._position = options.position ?? new Vec3(); + this._rotation = options.rotation ?? new Vec3(); + this._scale = options.scale ?? new Vec3(1, 1, 1); + + this._disabled = options.disabled ?? false; + + this._layers = options.layers ?? this._layers; + this._shading = options.shading ?? this._shading; + + if (options.defaultColor instanceof Color) { + this._defaultColor = options.defaultColor; + } + if (options.hoverColor instanceof Color) { + this._hoverColor = options.hoverColor; + } + if (options.disabledColor instanceof Color) { + this._disabledColor = options.disabledColor; + } + } + + set disabled(value) { + for (let i = 0; i < this.meshInstances.length; i++) { + setMeshColor(this.meshInstances[i].mesh, value ? this._disabledColor : this._defaultColor); + } + this._disabled = value ?? false; + } + + get disabled() { + return this._disabled; + } + + set shading(value) { + this._shading = value ?? true; + + const color = this._disabled ? this._disabledColor : this._defaultColor; + for (let i = 0; i < this.meshInstances.length; i++) { + const mesh = this.meshInstances[i].mesh; + mesh.getPositions(tmpG.positions); + mesh.getNormals(tmpG.normals); + const shadow = applyShadowColor( + tmpG, + color, + this._shading ? this.entity.getWorldTransform() : undefined + ); + shadingMeshMap.set(mesh, shadow); + setMeshColor(mesh, color); + } + } + + get shading() { + return this._shading; + } + + _createRoot(name) { + this.entity = new Entity(`${name}:${this.axis}`); + this.entity.setLocalPosition(this._position); + this.entity.setLocalEulerAngles(this._rotation); + this.entity.setLocalScale(this._scale); + } + + /** + * Create a mesh from a primitive. + * + * @param {Geometry} geom - The geometry to create the mesh from. + * @param {boolean} shading - Whether to apply shading to the primitive. + * @returns {Mesh} The mesh created from the primitive. + * @throws {Error} If the primitive type is invalid. + * @protected + */ + _createMesh(geom, shading = true) { + const color = this._disabled ? this._disabledColor : this._defaultColor; + const shadow = applyShadowColor( + geom, + color, + shading ? this.entity.getWorldTransform() : undefined + ); + const mesh = Mesh.fromGeometry(this.device, geom); + shadingMeshMap.set(mesh, shadow); + + return mesh; + } + + /** + * Create a render component for an entity. + * + * @param {Entity} entity - The entity to create the render component for. + * @param {Mesh[]} meshes - The meshes to create the render component with. + * @protected + */ + _createRenderComponent(entity, meshes) { + const material = new ShaderMaterial(shaderDesc); + 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], material); + meshInstances.push(mi); + this.meshInstances.push(mi); + } + entity.addComponent('render', { + meshInstances: meshInstances, + layers: this._layers, + castShadows: false + }); + } + + /** + * Add a render mesh to an entity. + * + * @param {Entity} entity - The entity to add the render mesh to. + * @param {string} type - The type of primitive to create. + * @param {boolean} shading - Whether to apply shading to the primitive. + * @throws {Error} If the primitive type is invalid. + * @protected + */ + _addRenderMesh(entity, type, shading) { + const Geometry = GEOMETRIES[type]; + if (!Geometry) { + throw new Error('Invalid primitive type.'); + } + this._createRenderComponent(entity, [ + this._createMesh(new Geometry(), shading) + ]); + } + + hover(state) { + if (this._disabled) { + return; + } + for (let i = 0; i < this.meshInstances.length; i++) { + const color = state ? this._hoverColor : this._defaultColor; + const mesh = this.meshInstances[i].mesh; + setMeshColor(mesh, color); + } + } + + destroy() { + this.entity.destroy(); + } +} + +export { Shape }; diff --git a/src/extras/gizmo/shape/sphere-shape.js b/src/extras/gizmo/shape/sphere-shape.js new file mode 100644 index 00000000000..592482841fb --- /dev/null +++ b/src/extras/gizmo/shape/sphere-shape.js @@ -0,0 +1,52 @@ +import { SphereGeometry } from '../../../scene/geometry/sphere-geometry.js'; +import { TriData } from '../tri-data.js'; +import { Shape } from './shape.js'; + +class SphereShape extends Shape { + _size = 0.12; + + _tolerance = 0.05; + + constructor(device, options = {}) { + super(device, options); + + this.triData = [ + new TriData(new SphereGeometry(), 2) + ]; + + this._createCenter(); + } + + _createCenter() { + this._createRoot('sphereCenter'); + this._updateTransform(); + + // box + this._addRenderMesh(this.entity, 'sphere', this._shading); + } + + set size(value) { + this._size = value ?? 1; + this._updateTransform(); + } + + get size() { + return this._size; + } + + set tolerance(value) { + this._tolerance = value; + this._updateTransform(); + } + + get tolerance() { + return this._tolerance; + } + + _updateTransform() { + // intersect/render + this.entity.setLocalScale(this._size, this._size, this._size); + } +} + +export { SphereShape }; diff --git a/src/extras/gizmo/transform-gizmo.js b/src/extras/gizmo/transform-gizmo.js index 27c3f878921..a2efb445346 100644 --- a/src/extras/gizmo/transform-gizmo.js +++ b/src/extras/gizmo/transform-gizmo.js @@ -19,8 +19,7 @@ import { GIZMOAXIS_X, GIZMOAXIS_XYZ, GIZMOAXIS_Y, GIZMOAXIS_Z } from './constant import { Gizmo } from './gizmo.js'; /** - * @import { AppBase } from '../../framework/app-base.js' - * @import { AxisShape } from './axis-shapes.js' + * @import { Shape } from './shape/shape.js' * @import { CameraComponent } from '../../framework/components/camera/component.js' * @import { Layer } from '../../scene/layer.js' * @import { MeshInstance } from '../../scene/mesh-instance.js' @@ -90,7 +89,7 @@ class TransformGizmo extends Gizmo { /** * Internal color for meshes. * - * @type {Object} + * @type {{ axis: Record, hover: Record, disabled: Color }} * @protected */ _meshColors = { @@ -114,7 +113,7 @@ class TransformGizmo extends Gizmo { /** * Internal version of the guide line color. * - * @type {Object} + * @type {Record} * @protected */ _guideColors = { @@ -150,17 +149,25 @@ class TransformGizmo extends Gizmo { _rootStartRot = new Quat(); /** - * Internal object containing the axis shapes to render. + * Internal state of if shading is enabled. Defaults to true. * - * @type {Object.} + * @type {boolean} + * @protected + */ + _shading = false; + + /** + * Internal object containing the gizmo shapes to render. + * + * @type {Object.} * @protected */ _shapes = {}; /** - * Internal mapping of mesh instances to axis shapes. + * Internal mapping of mesh instances to gizmo shapes. * - * @type {Map} + * @type {Map} * @private */ _shapeMap = new Map(); @@ -168,7 +175,7 @@ class TransformGizmo extends Gizmo { /** * Internal currently hovered shape. * - * @type {AxisShape | null} + * @type {Shape | null} * @private */ _hoverShape = null; @@ -247,16 +254,15 @@ class TransformGizmo extends Gizmo { /** * Creates a new TransformGizmo object. * - * @param {AppBase} app - The application instance. * @param {CameraComponent} camera - The camera component. * @param {Layer} layer - The render layer. * @example * const gizmo = new pc.TransformGizmo(app, camera, layer); */ - constructor(app, camera, layer) { - super(app, camera, layer); + constructor(camera, layer) { + super(camera, layer); - app.on('update', () => { + this._app.on('update', () => { if (!this.root.enabled) { return; } @@ -329,6 +335,28 @@ class TransformGizmo extends Gizmo { }); } + /** + * Sets whether shading are enabled. Defaults to true. + * + * @type {boolean} + */ + set shading(value) { + this._shading = this.root.enabled && value; + + for (const name in this._shapes) { + this._shapes[name].shading = this._shading; + } + } + + /** + * Gets whether shading are enabled. Defaults to true. + * + * @type {boolean} + */ + get shading() { + return this._shading; + } + /** * Sets whether snapping is enabled. Defaults to false. * @@ -490,7 +518,7 @@ class TransformGizmo extends Gizmo { } this._hoverAxis = this._getAxis(meshInstance); this._hoverIsPlane = this._getIsPlane(meshInstance); - const shape = meshInstance ? this._shapeMap.get(meshInstance) || null : null; + const shape = meshInstance ? this._shapeMap.get(meshInstance) ?? null : null; if (shape === this._hoverShape) { return; } @@ -711,11 +739,11 @@ class TransformGizmo extends Gizmo { * @override */ destroy() { + super.destroy(); + for (const key in this._shapes) { this._shapes[key].destroy(); } - - super.destroy(); } } diff --git a/src/extras/gizmo/translate-gizmo.js b/src/extras/gizmo/translate-gizmo.js index b0f3bc6ba35..e2bc689f6d4 100644 --- a/src/extras/gizmo/translate-gizmo.js +++ b/src/extras/gizmo/translate-gizmo.js @@ -1,7 +1,6 @@ import { Vec3 } from '../../core/math/vec3.js'; import { Quat } from '../../core/math/quat.js'; -import { AxisArrow, AxisPlane, AxisSphereCenter } from './axis-shapes.js'; import { GIZMOSPACE_LOCAL, GIZMOAXIS_FACE, @@ -10,9 +9,11 @@ import { GIZMOAXIS_Z } from './constants.js'; import { TransformGizmo } from './transform-gizmo.js'; +import { PlaneShape } from './shape/plane-shape.js'; +import { ArrowShape } from './shape/arrow-shape.js'; +import { SphereShape } from './shape/sphere-shape.js'; /** - * @import { AppBase } from '../../framework/app-base.js' * @import { CameraComponent } from '../../framework/components/camera/component.js' * @import { GraphNode } from '../../scene/graph-node.js' * @import { Layer } from '../../scene/layer.js' @@ -30,53 +31,57 @@ const tmpQ1 = new Quat(); */ class TranslateGizmo extends TransformGizmo { _shapes = { - face: new AxisSphereCenter(this._device, { + face: new SphereShape(this._device, { axis: GIZMOAXIS_FACE, layers: [this._layer.id], + shading: this._shading, defaultColor: this._meshColors.axis.xyz, hoverColor: this._meshColors.hover.xyz }), - yz: new AxisPlane(this._device, { + yz: new PlaneShape(this._device, { axis: GIZMOAXIS_X, - flipAxis: GIZMOAXIS_Y, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, -90), defaultColor: this._meshColors.axis.x, hoverColor: this._meshColors.hover.x }), - xz: new AxisPlane(this._device, { + xz: new PlaneShape(this._device, { axis: GIZMOAXIS_Y, - flipAxis: GIZMOAXIS_Z, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, 0), defaultColor: this._meshColors.axis.y, hoverColor: this._meshColors.hover.y }), - xy: new AxisPlane(this._device, { + xy: new PlaneShape(this._device, { axis: GIZMOAXIS_Z, - flipAxis: GIZMOAXIS_X, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(90, 0, 0), defaultColor: this._meshColors.axis.z, hoverColor: this._meshColors.hover.z }), - x: new AxisArrow(this._device, { + x: new ArrowShape(this._device, { axis: GIZMOAXIS_X, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, -90), defaultColor: this._meshColors.axis.x, hoverColor: this._meshColors.hover.x }), - y: new AxisArrow(this._device, { + y: new ArrowShape(this._device, { axis: GIZMOAXIS_Y, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(0, 0, 0), defaultColor: this._meshColors.axis.y, hoverColor: this._meshColors.hover.y }), - z: new AxisArrow(this._device, { + z: new ArrowShape(this._device, { axis: GIZMOAXIS_Z, layers: [this._layer.id], + shading: this._shading, rotation: new Vec3(90, 0, 0), defaultColor: this._meshColors.axis.z, hoverColor: this._meshColors.hover.z @@ -107,14 +112,13 @@ class TranslateGizmo extends TransformGizmo { /** * Creates a new TranslateGizmo object. * - * @param {AppBase} app - The application instance. * @param {CameraComponent} camera - The camera component. * @param {Layer} layer - The render layer. * @example * const gizmo = new pc.TranslateGizmo(app, camera, layer); */ - constructor(app, camera, layer) { - super(app, camera, layer); + constructor(camera, layer) { + super(camera, layer); this._createTransform(); diff --git a/src/extras/gizmo/tri-data.js b/src/extras/gizmo/tri-data.js index b4da0fdf677..888825dbb53 100644 --- a/src/extras/gizmo/tri-data.js +++ b/src/extras/gizmo/tri-data.js @@ -55,13 +55,27 @@ class TriData { return this._priority; } + /** + * Sets the transform of the triangle data. + * + * @param {Vec3} [pos] - The position of the transform. + * @param {Quat} [rot] - The rotation of the transform. + * @param {Vec3} [scale] - The scale of the transform. + */ setTransform(pos = new Vec3(), rot = new Quat(), scale = new Vec3()) { this.transform.setTRS(pos, rot, scale); } - calculateTris(geometry) { - const positions = geometry.positions; - const indices = geometry.indices; + /** + * @param {Geometry} geometry - The geometry to create the triangle data from. + */ + fromGeometry(geometry) { + if (!geometry || !(geometry instanceof Geometry)) { + throw new Error('No geometry provided.'); + } + + const positions = geometry.positions ?? []; + const indices = geometry.indices ?? []; this.tris = []; for (let k = 0; k < indices.length; k += 3) { const i1 = indices[k]; @@ -75,13 +89,6 @@ class TriData { this.tris.push(tri); } } - - fromGeometry(geometry) { - if (!geometry || !(geometry instanceof Geometry)) { - throw new Error('No geometry provided.'); - } - this.calculateTris(geometry); - } } export { TriData };