From 39c1fa13e5fb0f5b4cb91e2a923537515ae254b2 Mon Sep 17 00:00:00 2001 From: Cassandra Beckley Date: Thu, 14 Jan 2021 10:32:55 -0800 Subject: [PATCH] brush UI and soft brushes --- src/brushEngine.ts | 9 +++ src/components/BackgroundSettings.tsx | 50 +++++++++++++- src/components/BrushMaterial.tsx | 94 --------------------------- src/components/MeshPaint.tsx | 34 ++++++++-- src/components/TexturePaint.tsx | 18 ++++- src/components/Widget.tsx | 4 +- src/components/menu/BrushMaterial.tsx | 76 ++++++++++++++++++++++ src/components/menu/BrushSettings.tsx | 68 +++++++++++++++++++ src/components/menu/MenuBar.tsx | 69 ++++++++++++++++++++ src/components/menu/TabPanel.tsx | 39 +++++++++++ src/constants.ts | 3 + src/hooks/inertia.tsx | 37 +++++++++++ src/shaders/brush/2d.shader/frag.glsl | 12 +++- src/shaders/brush/3d.shader/frag.glsl | 15 ++++- src/slate.ts | 65 +++++++++--------- src/texpaint.css | 76 +++++++++++++++++----- src/texpaint.tsx | 57 ++-------------- src/widgets/environmentBall.ts | 6 +- src/widgets/meshDisplay.ts | 12 +++- src/windowManager.ts | 9 ++- 20 files changed, 541 insertions(+), 212 deletions(-) delete mode 100644 src/components/BrushMaterial.tsx create mode 100644 src/components/menu/BrushMaterial.tsx create mode 100644 src/components/menu/BrushSettings.tsx create mode 100644 src/components/menu/MenuBar.tsx create mode 100644 src/components/menu/TabPanel.tsx create mode 100644 src/hooks/inertia.tsx diff --git a/src/brushEngine.ts b/src/brushEngine.ts index 3636d4b..926a6e3 100644 --- a/src/brushEngine.ts +++ b/src/brushEngine.ts @@ -10,12 +10,15 @@ import fragBrush2dShader from './shaders/brush/2d.shader/frag.glsl'; import vertBrush3dShader from './shaders/brush/3d.shader/vert.glsl'; import fragBrush3dShader from './shaders/brush/3d.shader/frag.glsl'; +import Mesh from './mesh'; export default class BrushEngine { gl: WebGLRenderingContext; radius: number; spacing: number; + soft: boolean; + slate: Slate; segmentStart: vec2; @@ -44,6 +47,7 @@ export default class BrushEngine { const radius = diameter / 2; this.radius = radius; this.spacing = spacing; + this.soft = false; this.windowManager = windowManager; this.slate = windowManager.slate; @@ -273,6 +277,8 @@ export default class BrushEngine { modelViewMatrix ); + gl.uniform1i(this.brush2dShader.uniforms.uSoft, Number(this.soft)); + { const size = 2; const type = gl.FLOAT; // 32 bit floats @@ -360,6 +366,7 @@ export default class BrushEngine { // TODO: cover seams properly // TODO: maybe solve this by adding extra geometry at seams in a pre-process step when the mesh is loaded? + // TODO: or maybe they can be covered by drawing geometry as lines after triangles? const gl = this.gl; gl.disable(gl.CULL_FACE); @@ -412,6 +419,8 @@ export default class BrushEngine { this.slate.height ); + gl.uniform1i(this.brush3dShader.uniforms.uSoft, Number(this.soft)); + { const size = 3; const type = gl.FLOAT; // 32 bit floats diff --git a/src/components/BackgroundSettings.tsx b/src/components/BackgroundSettings.tsx index c07246d..c949f3e 100644 --- a/src/components/BackgroundSettings.tsx +++ b/src/components/BackgroundSettings.tsx @@ -1,10 +1,56 @@ import * as React from 'react'; +import { useState } from 'react'; import Widget from './Widget'; +import useInertia from '../hooks/inertia'; import EnvironmentBall from '../widgets/environmentBall'; +import { ROTATE_SENSITIVITY } from '../constants'; + +export default function BackgroundSettings({ + rotation, + backgroundOffset, + setBackgroundOffset, +}) { + const [lastPosition, setLastPosition] = useState(0); + const [rotating, setRotating] = useState(false); + + const stopRotating = (e: React.PointerEvent) => { + if (rotating) { + setRotating(false); + } + }; + + const handlePointerDown = (e: React.PointerEvent) => { + if (e.button === 0) { + setLastPosition(e.clientX); + setRotating(true); + } + }; + const handlePointerUp = (e: React.PointerEvent) => { + stopRotating(e); + }; + const handlePointerMove = (e: React.PointerEvent) => { + if (rotating) { + const deltaAngle = (lastPosition - e.clientX) * -ROTATE_SENSITIVITY; + + setBackgroundOffset(backgroundOffset + deltaAngle); + setLastPosition(e.clientX); + } + }; + const handlePointerLeave = (e: React.PointerEvent) => { + stopRotating(e); + }; -export default function BackgroundSettings({ rotation, backgroundOffset }) { return ( - + ); } diff --git a/src/components/BrushMaterial.tsx b/src/components/BrushMaterial.tsx deleted file mode 100644 index 5ffe563..0000000 --- a/src/components/BrushMaterial.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { vec3 } from 'gl-matrix'; -import * as React from 'react'; -import { useContext, useState } from 'react'; -import { HALF_COLOR } from '../constants'; -import ColorWheel from './ColorWheel'; -import { WindowContext } from './Widget'; - -export default function BrushMaterial() { - const windowManager = useContext(WindowContext); - - const [brushColor, setBrushColor] = useState(vec3.create()); - const [roughness, setRoughness] = useState(0.5); - const [metallic, setMetallic] = useState(0); - const [showMaterialSelector, setShowColorSelector] = useState(false); - - const color = vec3.create(); - vec3.mul(color, brushColor, [255, 255, 255]); - vec3.round(color, color); - - // TODO: use http://danielstern.ca/range.css/ to style the range inputs - - return ( -
-
- ); -} diff --git a/src/components/MeshPaint.tsx b/src/components/MeshPaint.tsx index 3300727..743ba3d 100644 --- a/src/components/MeshPaint.tsx +++ b/src/components/MeshPaint.tsx @@ -102,22 +102,22 @@ export default function MeshPaint({}) { const handleRotateBackgroundStart = (x: number) => { setRotatingBackground(true); setLastBackgroundOffset(x); - } + }; const handleRotateBackgroundStop = () => { setRotatingBackground(false); - } + }; const handleRotateBackgroundMove = (x: number) => { const deltaAngle = (lastBackgroundOffset - x) * -ROTATE_SENSITIVITY; - + setBackgroundOffset(backgroundOffset + deltaAngle); setLastBackgroundOffset(x); if (!preventContext) { setPreventContext(true); } - } + }; const handlePanStart = (panPosition: vec3) => { setPan(true); @@ -292,9 +292,29 @@ export default function MeshPaint({}) { onPointerLeave={handlePointerLeave} onContextMenu={handleContextMenu} > -
- -
+
+ +
diff --git a/src/components/TexturePaint.tsx b/src/components/TexturePaint.tsx index 3382cfa..ebbd411 100644 --- a/src/components/TexturePaint.tsx +++ b/src/components/TexturePaint.tsx @@ -1,6 +1,6 @@ import { mat4, vec2, vec3 } from 'gl-matrix'; import * as React from 'react'; -import { useContext, useRef, useState } from 'react'; +import { useContext, useEffect, useRef, useState } from 'react'; import { SCROLL_SCALE } from '../constants'; import { normalizeWheelEvent } from '../utils'; import TextureDisplay, { getModelMatrix } from '../widgets/textureDisplay'; @@ -16,6 +16,8 @@ export default function TexturePaint() { const [view, setView] = useState(() => { const viewMatrix = mat4.create(); mat4.identity(viewMatrix); + // start offscreen before div bounds can be calculated + mat4.translate(viewMatrix, viewMatrix, [-10000, -10000, 0]); return viewMatrix; }); @@ -29,6 +31,20 @@ export default function TexturePaint() { const div = useRef(null); + useEffect(() => { + const bounds = div.current.getBoundingClientRect(); + + const adjusted = mat4.create(); + mat4.identity(adjusted); + mat4.translate(adjusted, adjusted, [ + bounds.width / 2 - windowManager.slate.width / 2, + bounds.height / 2 - windowManager.slate.height / 2, + 0, + ]); + + setView(adjusted); + }, []); + const uiToImageCoordinates = (uiCoord: vec2) => { const modelMatrix = getModelMatrix( windowManager.slate.width, diff --git a/src/components/Widget.tsx b/src/components/Widget.tsx index 9a927f9..eef5e67 100644 --- a/src/components/Widget.tsx +++ b/src/components/Widget.tsx @@ -108,9 +108,7 @@ export default function Widget({ } } - return ( -
- ); + return
; } export const WindowContext: React.Context = createContext(null); diff --git a/src/components/menu/BrushMaterial.tsx b/src/components/menu/BrushMaterial.tsx new file mode 100644 index 0000000..3b66402 --- /dev/null +++ b/src/components/menu/BrushMaterial.tsx @@ -0,0 +1,76 @@ +import { vec3 } from 'gl-matrix'; +import * as React from 'react'; +import { useContext, useState } from 'react'; +import { HALF_COLOR } from '../../constants'; +import ColorWheel from '../ColorWheel'; +import TabPanel from './TabPanel'; +import { WindowContext } from '../Widget'; + +export default function BrushMaterial({ visible, onClick }) { + const windowManager = useContext(WindowContext); + + const [brushColor, setBrushColor] = useState(vec3.create()); + const [roughness, setRoughness] = useState(0.5); + const [metallic, setMetallic] = useState(0); + + const color = vec3.create(); + vec3.mul(color, brushColor, [255, 255, 255]); + vec3.round(color, color); + + // TODO: use http://danielstern.ca/range.css/ to style the range inputs + + return ( + +
+ { + setBrushColor(c); + windowManager.slate.brushColor = c; + }} + /> +
+ + +
+ + ); +} diff --git a/src/components/menu/BrushSettings.tsx b/src/components/menu/BrushSettings.tsx new file mode 100644 index 0000000..99d68fb --- /dev/null +++ b/src/components/menu/BrushSettings.tsx @@ -0,0 +1,68 @@ +import * as React from 'react'; +import { useContext, useState } from 'react'; +import TabPanel from './TabPanel'; +import { WindowContext } from '../Widget'; + +export default function BrushMaterial({ visible, onClick }) { + const windowManager = useContext(WindowContext); + + const [soft, setSoft] = useState(false); + const [radius, setRadius] = useState( + () => windowManager.brushEngine.radius + ); // TODO: brush radius should percentage of max dimensions of texture/mesh + const [spacing, setSpacing] = useState( + () => windowManager.brushEngine.spacing + ); + + return ( + +
TODO: show brush preview
+
TODO: show brush tip
+ + + + TODO: multiple preset brushes, with options to save new presets and + to reset to defaults +
+ ); +} diff --git a/src/components/menu/MenuBar.tsx b/src/components/menu/MenuBar.tsx new file mode 100644 index 0000000..19a9f18 --- /dev/null +++ b/src/components/menu/MenuBar.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { useContext, useState } from 'react'; +import { WindowContext } from '../Widget'; + +import { loadAssetFromBlob } from '../../loader'; +import { AssetType } from '../../loader/asset'; +import BrushMaterial from './BrushMaterial'; +import BrushSettings from './BrushSettings'; + +enum SettingsTab { + None, + Material, + Settings, +} + +export default function MenuBar({ on2d, on3d }) { + const windowManager = useContext(WindowContext); + + const [openTab, setOpenTab] = useState(SettingsTab.None); + + const toggleTab = (tab: SettingsTab) => { + if (openTab === tab) { + setOpenTab(SettingsTab.None); + } else { + setOpenTab(tab); + } + }; + + const handleOpen = () => { + const input = document.createElement('input'); + input.type = 'file'; + input.click(); + + input.addEventListener('change', function () { + const file = this.files[0]; + + (async () => { + const asset = await loadAssetFromBlob(file.name, file); + switch (asset.type) { + case AssetType.Image: + windowManager.slate.loadAlbedo(asset.image); + break; + case AssetType.Mesh: + const mesh = asset.meshes[0]; + windowManager.setMesh(mesh); + break; + } + windowManager.drawOnNextFrame(); + })(); + }); + }; + + return ( +
+ toggleTab(SettingsTab.Material)} + /> + toggleTab(SettingsTab.Settings)} + /> +
+ + + +
+ ); +} diff --git a/src/components/menu/TabPanel.tsx b/src/components/menu/TabPanel.tsx new file mode 100644 index 0000000..73a087f --- /dev/null +++ b/src/components/menu/TabPanel.tsx @@ -0,0 +1,39 @@ +import * as React from 'react'; +import { HALF_COLOR } from '../../constants'; + +export default function BrushMaterial({ + label = null, + background = false, + buttonClass = null, + buttonStyle = null, + children = null, + showPanel = false, + onClick = () => {}, +}) { + return ( +
+ + + {showPanel && ( +
+ {children} +
+ )} +
+ ); +} diff --git a/src/constants.ts b/src/constants.ts index b3faba4..8db0bea 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -12,3 +12,6 @@ export const FAR = 100.0; export const HALF_COLOR = '#7f7f7f'; export const BACKGROUND_DARK = '#222222'; export const BACKGROUND_LIGHT = '#383838'; + +// brush defaults; will be replaced by customizable brush library probably +export const DEFAULT_BRUSH_SPACING = 0.2; diff --git a/src/hooks/inertia.tsx b/src/hooks/inertia.tsx new file mode 100644 index 0000000..e4287ca --- /dev/null +++ b/src/hooks/inertia.tsx @@ -0,0 +1,37 @@ +import { useEffect, useRef, useState } from 'react'; + +const SLOWDOWN = 0.8; + +export default function useInertia(update: (v: number) => {}) { + const [velocity, setVelocity] = useState(0); + + const ref = useRef({ + timer: null, + velocity: 0, + lastUpdate: performance.now(), + }); + + console.log('current velocity:', velocity); + + const handleUpdate = (ts: DOMHighResTimeStamp) => { + const deltaTime = ts - ref.current.lastUpdate; + ref.current.lastUpdate = ts; + + ref.current.velocity *= SLOWDOWN * deltaTime; + update(ref.current.velocity); + + if (ref.current.velocity > Number.EPSILON) { + const t = requestAnimationFrame(handleUpdate); + ref.current.timer = t; + } + }; + + useEffect(() => { + cancelAnimationFrame(ref.current.timer); + ref.current.velocity = velocity; + + handleUpdate(performance.now()); + }, [velocity]); + + return setVelocity; +} diff --git a/src/shaders/brush/2d.shader/frag.glsl b/src/shaders/brush/2d.shader/frag.glsl index c65bd0a..ef542f2 100644 --- a/src/shaders/brush/2d.shader/frag.glsl +++ b/src/shaders/brush/2d.shader/frag.glsl @@ -1,9 +1,19 @@ precision mediump float; +uniform bool uSoft; + varying highp vec2 vTextureCoord; varying float vPixelWidth; void main() { - float mask = smoothstep(0.5, 0.5 + vPixelWidth * 2.0, 1.0 - length(vTextureCoord - 0.5)); + float mask = 0.0; + + if (!uSoft) { + mask = smoothstep(0.5, 0.5 + vPixelWidth * 2.0, 1.0 - length(vTextureCoord - 0.5)); + } else { + float linear = clamp(1.0 - length(vTextureCoord - 0.5) * 2.0, 0.0, 1.0); + mask = linear * linear; + } + gl_FragColor = vec4(mask); } \ No newline at end of file diff --git a/src/shaders/brush/3d.shader/frag.glsl b/src/shaders/brush/3d.shader/frag.glsl index f5bf53f..1cc5117 100644 --- a/src/shaders/brush/3d.shader/frag.glsl +++ b/src/shaders/brush/3d.shader/frag.glsl @@ -6,8 +6,19 @@ varying float vPixelWidth; uniform vec3 uCenter; uniform float uRadius; +uniform bool uSoft; + void main() { - // float mask = 1.0 - step(uRadius, length(uCenter - vWorldPosition)); - float mask = 1.0 - smoothstep(uRadius, uRadius + vPixelWidth * 2.0, length(uCenter - vWorldPosition)); + float mask = 0.0; + + float distance = length(uCenter - vWorldPosition); + + if (!uSoft) { + mask = 1.0 - smoothstep(uRadius, uRadius + vPixelWidth * 2.0, distance); + } else { + float linear = clamp(1.0 - (distance / uRadius), 0.0, 1.0); + mask = linear * linear; + } + gl_FragColor = vec4(mask); } \ No newline at end of file diff --git a/src/slate.ts b/src/slate.ts index 94a28a2..1cc19b7 100644 --- a/src/slate.ts +++ b/src/slate.ts @@ -195,12 +195,34 @@ export default class Slate { set brushRoughness(roughness: number) { const roughnessByte = roughness * 255; - fillTexture(this.gl, this.brushRoughnessTexture, 1, 1, new Uint8ClampedArray([roughnessByte, roughnessByte, roughnessByte, 255])); + fillTexture( + this.gl, + this.brushRoughnessTexture, + 1, + 1, + new Uint8ClampedArray([ + roughnessByte, + roughnessByte, + roughnessByte, + 255, + ]) + ); } set brushMetallic(metallic: number) { const metallicByte = metallic * 255; - fillTexture(this.gl, this.brushMetallicTexture, 1, 1, new Uint8ClampedArray([metallicByte, metallicByte, metallicByte, 255])); + fillTexture( + this.gl, + this.brushMetallicTexture, + 1, + 1, + new Uint8ClampedArray([ + metallicByte, + metallicByte, + metallicByte, + 255, + ]) + ); } loadAlbedo(image: Image) { @@ -246,26 +268,9 @@ export default class Slate { // Undo history apply() { - this.compositor.run( - this.albedo, - O(this.layer.albedo).mix( - O(this.brushAlbedo).mask(this.currentOperation) - ) - ); + this.updated = true; - this.compositor.run( - this.roughness, - O(this.layer.roughness).mix( - O(this.brushRoughnessTexture).mask(this.currentOperation) - ) - ); - - this.compositor.run( - this.metallic, - O(this.layer.metallic).mix( - O(this.brushMetallicTexture).mask(this.currentOperation) - ) - ); + this.updateTextures(); fillTexture( this.gl, @@ -275,15 +280,17 @@ export default class Slate { new Uint8ClampedArray([0, 0, 0, 255]) ); - this.layer = new Layer( - this.gl, - this.width, - this.height - ); + this.layer = new Layer(this.gl, this.width, this.height); this.compositor.run(this.layer.albedo, O(this.albedo).mix(this.albedo)); - this.compositor.run(this.layer.roughness, O(this.roughness).mix(this.roughness)); - this.compositor.run(this.layer.metallic, O(this.metallic).mix(this.metallic)); + this.compositor.run( + this.layer.roughness, + O(this.roughness).mix(this.roughness) + ); + this.compositor.run( + this.layer.metallic, + O(this.metallic).mix(this.metallic) + ); // save image state in undo queue @@ -344,6 +351,6 @@ export default class Slate { ) ); - // this.compositor.run(this.albedo, O(this.metallic).mix(this.metallic)); + // this.compositor.run(this.albedo, O(this.currentOperation).mix(this.currentOperation)); } } diff --git a/src/texpaint.css b/src/texpaint.css index dfc2b4b..2c0c259 100644 --- a/src/texpaint.css +++ b/src/texpaint.css @@ -1,10 +1,19 @@ +html { + box-sizing: border-box; +} +*, +*:before, +*:after { + box-sizing: inherit; +} + body { position: absolute; margin: 0; border: 0; touch-action: none; - width: 100%; - height: 100%; + width: 100vw; + height: 100vh; background-color: #383838; @@ -18,32 +27,64 @@ body { overflow: hidden; } -.top-bar { +.menu-bar { + /* TODO: on mobile, show at top in portrait mode and at side in landscape */ + box-sizing: border-box; background-color: #222222; - height: 60px; - padding: 0 40px; + height: 100%; + padding: 40px 10px; + width: 60px; display: flex; + flex-direction: column; z-index: 10; } -.top-bar button { - display: block; - height: 40px; - margin: 10px; - margin-left: 0; - padding: 0 20px; +.menu-bar div.spacer { + flex-grow: 1; } -.color-select { +.menu-bar button { + width: 40px; + margin-top: 10px; + padding: 10px 0; +} + +.tab-panel { + box-sizing: border-box; position: relative; - display: inline-block; - width: 50px; - height: 100%; + border-radius: 0 10px 10px 0; + margin-left: -10px; + margin-top: -10px; + margin-right: -10px; + padding-left: 10px; + padding: 10px; +} - border-radius: 10px 10px 0 0; +.tab-panel button { + margin: 0; +} + +.tab-panel .panel { + position: absolute; + display: block; + right: 60px; + top: 0; + + width: 350px; + + /* TODO: shared UI constants */ + border-radius: 10px 0 10px 10px; + overflow: hidden; } -.color-select input { +.tab-panel .border-top { + height: 10px; + background-color: #7f7f7f; + width: 100%; +} + +.tab-panel input[type='range'] { + display: block; width: 100%; } @@ -51,7 +92,6 @@ button.brush-color { display: block; position: relative; margin: 0 auto; - margin-top: 10px; width: 40px; height: 40px; border: 2px solid #7f7f7f; diff --git a/src/texpaint.tsx b/src/texpaint.tsx index bca19ca..99b3ede 100644 --- a/src/texpaint.tsx +++ b/src/texpaint.tsx @@ -1,18 +1,16 @@ import { vec3 } from 'gl-matrix'; import * as React from 'react'; -import { useContext, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import ColorSelect from './widgets/colorSelect'; -import BrushMaterial from './components/BrushMaterial'; import { WindowContext } from './components/Widget'; import WindowManager from './windowManager'; import TextureDisplay from './widgets/textureDisplay'; +import MenuBar from './components/menu/MenuBar'; import TexturePaint from './components/TexturePaint'; import MeshPaint from './components/MeshPaint'; import MeshDisplay from './widgets/meshDisplay'; import Widget from './widget'; -import { loadAssetFromBlob } from './loader'; -import { AssetType } from './loader/asset'; import ImageWidget from './widgets/imageWidget'; import ViewAssetCache from './components/debug/ViewAssetCache'; import ViewShaderCache from './components/debug/ViewShaderCache'; @@ -64,45 +62,6 @@ const Renderer = ({ ); }; -const TopBar = ({ on2d, on3d }) => { - const windowManager = useContext(WindowContext); - - const handleOpen = () => { - const input = document.createElement('input'); - input.type = 'file'; - input.click(); - - input.addEventListener('change', function () { - const file = this.files[0]; - - (async () => { - const asset = await loadAssetFromBlob(file.name, file); - switch (asset.type) { - case AssetType.Image: - windowManager.slate.loadAlbedo(asset.image); - break; - case AssetType.Mesh: - const mesh = asset.meshes[0]; - windowManager.setMesh(mesh); - break; - } - windowManager.drawOnNextFrame(); - })(); - }); - }; - - return ( -
- - - -
- -
-
- ); -}; - // TODO: switch to using CSS (maybe modules) const App = () => { @@ -117,9 +76,7 @@ const App = () => { }); return ( -
+
{ ImageWidget, ]} > - setShowTexture(!showTexture)} - on3d={() => setShowMesh(!showMesh)} - />
{ )}
+ setShowTexture(!showTexture)} + on3d={() => setShowMesh(!showMesh)} + />
); diff --git a/src/widgets/environmentBall.ts b/src/widgets/environmentBall.ts index 1c1b10b..217d8a5 100644 --- a/src/widgets/environmentBall.ts +++ b/src/widgets/environmentBall.ts @@ -121,7 +121,11 @@ export default class EnvironmentBall { mat4.invert(invRotation, invRotation); mat4.rotateY(invRotation, invRotation, Math.PI); - gl.uniformMatrix4fv(shader.uniforms.uRotationMatrix, false, invRotation); + gl.uniformMatrix4fv( + shader.uniforms.uRotationMatrix, + false, + invRotation + ); { const size = 2; diff --git a/src/widgets/meshDisplay.ts b/src/widgets/meshDisplay.ts index 97f19d1..e44f378 100644 --- a/src/widgets/meshDisplay.ts +++ b/src/widgets/meshDisplay.ts @@ -369,7 +369,15 @@ export default class MeshDisplay { width: number, height: number, mesh: Mesh, - { position, rotation, scale, brushCursor, brushNormal, brushRadius, backgroundOffset } + { + position, + rotation, + scale, + brushCursor, + brushNormal, + brushRadius, + backgroundOffset, + } ) { gl.enable(gl.DEPTH_TEST); @@ -436,7 +444,7 @@ export default class MeshDisplay { gl: WebGLRenderingContext, rotation: quat, projectionMatrix: mat4, - backgroundOffset: number, + backgroundOffset: number ) { gl.disable(gl.CULL_FACE); gl.useProgram(this.backgroundShader.program); diff --git a/src/windowManager.ts b/src/windowManager.ts index 377c3ae..94dd4ea 100644 --- a/src/windowManager.ts +++ b/src/windowManager.ts @@ -1,12 +1,13 @@ import { mat4, vec2, vec3 } from 'gl-matrix'; import BrushEngine from './brushEngine'; +import { DEFAULT_BRUSH_SPACING } from './constants'; import Image, { ImageFormat, ImageStorage } from './loader/image'; import MeshData from './loader/meshData'; import Mesh from './mesh'; import Slate from './slate'; import type Widget from './widget'; -const brushSize = 40.0; +const brushSize = 40.0; // TODO: brush size should be percentage of max dimension of texture/mesh; default should be a constant const brushColor = vec3.create(); vec3.set(brushColor, 0, 0, 0); @@ -77,7 +78,11 @@ export default class WindowManager { this.slate = new Slate(this, 1024, 1024); this.mesh = null; - this.brushEngine = new BrushEngine(brushSize, 0.4, this); + this.brushEngine = new BrushEngine( + brushSize, + DEFAULT_BRUSH_SPACING, + this + ); } setViewport(x: number, y: number, width: number, height: number) {