├── .gitignore ├── LICENSE ├── README.md ├── craco.config.js ├── package-lock.json ├── package.json ├── public ├── CNAME ├── favicon.png ├── index.html ├── robots.txt ├── type.gif └── type.png ├── src ├── Actions.tsx ├── App.tsx ├── ArchaicText.tsx ├── Constants.tsx ├── Cursor.tsx ├── Hud.tsx ├── Keyboard.tsx ├── LineHandles.tsx ├── OldPointer.tsx ├── Pointer.tsx ├── PointerUtils.tsx ├── RawPointer.tsx ├── Rotators.tsx ├── State.tsx ├── Text.tsx ├── font.css ├── index.css ├── index.tsx ├── react-app-env.d.ts └── reportWebVitals.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Constraint Systems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Type 2 | 3 | 6 | 7 | A directed typing experiment. You choose the direction the letters should flow. 8 | 9 | https://type.constraint.systems 10 | 11 | ## About the tech 12 | 13 | Type uses a WebGL renderer built with three.js. It assembles a canvas sprite sheet of the letters, and uses that for a texture, which is placed on instanced rectangles. 14 | 15 | ## Dev 16 | 17 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 18 | 19 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | // craco.config.js 2 | module.exports = { 3 | style: { 4 | postcss: { 5 | plugins: [require("tailwindcss"), require("autoprefixer")], 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textvector", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@craco/craco": "^6.3.0", 7 | "@testing-library/jest-dom": "^5.14.1", 8 | "@testing-library/react": "^11.2.7", 9 | "@testing-library/user-event": "^12.8.3", 10 | "@types/jest": "^26.0.24", 11 | "@types/node": "^12.20.27", 12 | "@types/react": "^17.0.24", 13 | "@types/react-dom": "^17.0.9", 14 | "@types/three": "^0.132.1", 15 | "gh-pages": "^3.2.3", 16 | "lodash": "^4.17.21", 17 | "react": "^17.0.2", 18 | "react-dom": "^17.0.2", 19 | "react-scripts": "4.0.3", 20 | "three": "^0.132.2", 21 | "typescript": "^4.4.3", 22 | "web-vitals": "^1.1.2" 23 | }, 24 | "scripts": { 25 | "start": "craco start", 26 | "build": "craco build", 27 | "predeploy": "craco build", 28 | "deploy": "gh-pages -d build", 29 | "test": "craco test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "autoprefixer": "^9.8.7", 52 | "postcss": "^7.0.38", 53 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.16" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | type.constraint.systems 2 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/constraint-systems/type/822966ab107a85bea01e586fbcaa4b9706e57c28/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Type 10 | 15 | 16 | 17 | 22 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/type.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/constraint-systems/type/822966ab107a85bea01e586fbcaa4b9706e57c28/public/type.gif -------------------------------------------------------------------------------- /public/type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/constraint-systems/type/822966ab107a85bea01e586fbcaa4b9706e57c28/public/type.png -------------------------------------------------------------------------------- /src/Actions.tsx: -------------------------------------------------------------------------------- 1 | import State from "./State"; 2 | import * as THREE from "three"; 3 | 4 | export const setRay = ( 5 | state: State, 6 | targetVector: THREE.Vector3, 7 | mouse: THREE.Vector2, 8 | newZ: number 9 | ) => { 10 | const rayVec = state.tempVec.set( 11 | (mouse.x / window.innerWidth) * 2 - 1, 12 | -(mouse.y / window.innerHeight) * 2 + 1, 13 | 0.5 14 | ); 15 | const camera = state.camera; 16 | rayVec.unproject(camera); 17 | rayVec.sub(camera.position).normalize(); 18 | const distance = (newZ - camera.position.z) / rayVec.z; 19 | targetVector.copy(camera.position).add(rayVec.multiplyScalar(distance)); 20 | }; 21 | 22 | export const updateVector = (state: State): void => { 23 | const visibleHeight = 24 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * state.camera.position.z; 25 | const zoomPixel = visibleHeight / window.innerHeight; 26 | 27 | const worldMouse = [0, 0]; 28 | worldMouse[0] = 29 | (state.camera.position.x / zoomPixel + 30 | (state.mouse.x - window.innerWidth / 2)) * 31 | zoomPixel; 32 | worldMouse[1] = 33 | (state.camera.position.y / zoomPixel - 34 | (state.mouse.y - window.innerHeight / 2)) * 35 | zoomPixel; 36 | 37 | const start = state.text.linePositions[state.text.activeLine] || [0, 0]; 38 | state.vector.set( 39 | worldMouse[0] - start[0] - state.lastPosition[0], 40 | worldMouse[1] - start[1] - state.lastPosition[1] 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useRef } from "react"; 2 | import State from "./State"; 3 | import Keyboard from "./Keyboard"; 4 | import Pointer from "./Pointer"; 5 | import Hud from "./Hud"; 6 | import { BACKGROUND_COLOR, SAVE2X, TEXT_COLOR, TRANSPARENT } from "./Constants"; 7 | 8 | function App() { 9 | const canvasRef = useRef(null!); 10 | const printCanvasRef = useRef(null!); 11 | const [state, setState] = useState(null); 12 | const [settingsOpen, setSettingsOpen] = useState(false); 13 | const [aboutOpen, setAboutOpen] = useState(false); 14 | const [backgroundColor, setBackgroundColor] = useState(BACKGROUND_COLOR); 15 | const [textColor, setTextColor] = useState(TEXT_COLOR); 16 | const [transparentBackground, setTransparentBackground] = 17 | useState(TRANSPARENT); 18 | const [save2x, setSave2x] = useState(SAVE2X); 19 | const keyboardRef = useRef(null); 20 | 21 | useEffect(() => { 22 | document.fonts.load('16px "custom"').then(() => { 23 | const newState = new State(canvasRef.current, printCanvasRef.current); 24 | setState(newState); 25 | }); 26 | }, []); 27 | 28 | useEffect(() => { 29 | if (state) { 30 | state.setBackgroundColor(backgroundColor); 31 | } 32 | }, [state, backgroundColor]); 33 | 34 | useEffect(() => { 35 | if (state) { 36 | state.text.setColor(textColor); 37 | } 38 | }, [state, textColor]); 39 | 40 | useEffect(() => { 41 | if (state) { 42 | state.transparentBackground = transparentBackground; 43 | } 44 | }, [state, transparentBackground]); 45 | 46 | useEffect(() => { 47 | if (state) { 48 | state.save2x = save2x; 49 | } 50 | }, [state, save2x]); 51 | 52 | return ( 53 | <> 54 | 55 | 56 | {state ? ( 57 | <> 58 | 59 | 60 | 75 | 76 | ) : null} 77 | 88 | 89 | ); 90 | } 91 | 92 | export default App; 93 | -------------------------------------------------------------------------------- /src/ArchaicText.tsx: -------------------------------------------------------------------------------- 1 | // import * as THREE from "three"; 2 | // import { Euler } from "three"; 3 | // import State from "./State"; 4 | 5 | // const LIMIT = 1000; 6 | // class ArchaicText extends THREE.InstancedMesh { 7 | // chars: Array; 8 | // state: State; 9 | // aspect: number; 10 | // lineStarts: Array<[number, number]>; 11 | // lines: Array; 12 | // positions: Array<[number, number]>; 13 | // activeLine: number; 14 | 15 | // constructor(state: State) { 16 | // const geometry = new THREE.PlaneBufferGeometry(); 17 | // var uv = geometry.getAttribute("uv"); 18 | // let texture; 19 | // let texScale = [1, 1]; 20 | // let aspect; 21 | // const chars = " abcdefghijklmnopqrstuvwxyz?,.!1234567890".split(""); 22 | // { 23 | // const c = document.createElement("canvas"); 24 | // const cx = c.getContext("2d")!; 25 | // cx.clearRect(0, 0, c.width, c.height); 26 | // const fs = 64; 27 | // cx.font = fs + "px custom"; 28 | 29 | // const ch = Math.round(fs * 1.2); 30 | 31 | // const toMeasure = cx.measureText("n"); 32 | // const cw = toMeasure.width; 33 | 34 | // c.width = cw * chars.length; 35 | // c.height = ch; 36 | // // have to set font again after resize 37 | // cx.font = fs + "px custom"; 38 | 39 | // cx.fillStyle = "black"; 40 | // cx.textBaseline = "middle"; 41 | // for (let i = 0; i < chars.length; i++) { 42 | // const char = chars[i]; 43 | // cx.fillText(char, i * cw, ch / 2); 44 | // } 45 | // // document.body.appendChild(c); 46 | // texture = new THREE.CanvasTexture(c); 47 | // // texture.magFilter = THREE.NearestFilter; 48 | 49 | // uv.setXY(0, 0, 1); 50 | // uv.setXY(1, 1, 1); 51 | // uv.setXY(2, 0, 0); 52 | // uv.setXY(3, 1, 0); 53 | // texScale[0] = cw / c.width; 54 | // texScale[1] = ch / c.height; 55 | 56 | // aspect = [cw / ch, 1, 1]; 57 | // // aspect = [1, 1, 1]; 58 | // } 59 | 60 | // const visible = Array(LIMIT).fill(1); 61 | 62 | // geometry.setAttribute( 63 | // "visible", 64 | // new THREE.InstancedBufferAttribute(new Float32Array(visible), 1, false) 65 | // ); 66 | 67 | // const offsets = []; 68 | // for (let i = 0; i < LIMIT; i++) { 69 | // offsets.push(0, 0); 70 | // } 71 | 72 | // geometry.setAttribute( 73 | // "offset", 74 | // new THREE.InstancedBufferAttribute(new Float32Array(offsets), 2, false) 75 | // ); 76 | 77 | // const vertexShader = ` 78 | // varying vec2 vUv; 79 | // attribute vec2 offset; 80 | // varying vec2 vOffset; 81 | // uniform vec2 texScale; 82 | // varying vec2 vTexScale; 83 | // uniform vec3 aspect; 84 | // uniform float scale; 85 | // attribute float visible; 86 | 87 | // void main() { 88 | // vUv = uv * texScale; 89 | // vOffset = offset * texScale; 90 | // vTexScale = texScale; 91 | 92 | // gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position * aspect * scale, 1.0) * visible; 93 | // } 94 | // `; 95 | 96 | // const fragmentShader = ` 97 | // uniform sampler2D texture1; 98 | // varying vec2 vUv; 99 | // varying vec2 vOffset; 100 | // varying vec2 vTexScale; 101 | 102 | // void main() { 103 | // vec4 color = texture2D(texture1, vec2(vUv.x + vOffset.x, vUv.y + vOffset.y)); 104 | // gl_FragColor = color; 105 | // } 106 | // `; 107 | 108 | // var uniforms = { 109 | // texture1: { type: "t", value: texture }, 110 | // texScale: { value: texScale }, 111 | // aspect: { value: aspect }, 112 | // scale: { value: 0.5 }, 113 | // }; 114 | 115 | // const material = new THREE.ShaderMaterial({ 116 | // uniforms: uniforms, 117 | // vertexShader: vertexShader, 118 | // fragmentShader: fragmentShader, 119 | // }); 120 | // material.transparent = true; 121 | 122 | // super(geometry, material, LIMIT); 123 | // this.positions = []; 124 | // this.lines = [""]; 125 | // this.activeLine = 0; 126 | // this.chars = chars; 127 | // this.state = state; 128 | // this.aspect = aspect[0]; 129 | // this.lineStarts = [[0, 0]]; 130 | 131 | // state.scene.add(this); 132 | 133 | // this.setChars(); 134 | 135 | // // setInterval(() => { 136 | // // this.addText(chars[Math.floor(Math.random() * chars.length)]); 137 | // // }, 20); 138 | // } 139 | 140 | // getPositionFromAngle( 141 | // prev: [number, number], 142 | // angle: number 143 | // ): [number, number] { 144 | // const rx = 0.6 * this.aspect; 145 | // const x = prev[0] + Math.cos(angle) * rx; 146 | // const y = prev[1] + Math.sin(angle) * rx; 147 | // return [x, y]; 148 | // } 149 | 150 | // setChars() { 151 | // let counter = 0; 152 | // const offsetBuffer = this.geometry.attributes.offset.array; 153 | // // @ts-ignore 154 | // offsetBuffer.fill(0); 155 | // for (const line of this.lines) { 156 | // for (const char of line.split("")) { 157 | // // @ts-ignore 158 | // offsetBuffer[counter * 2] = this.chars.indexOf(char); 159 | // counter++; 160 | // } 161 | // } 162 | // this.geometry.attributes.offset.needsUpdate = true; 163 | // } 164 | 165 | // addText(data: string) { 166 | // if (!this.state.dragging) { 167 | // const rad = Math.atan2(this.state.vector.y, this.state.vector.x); 168 | // this.lines[this.activeLine] += data; 169 | // const previous = this.state.lastPosition.slice() as [number, number]; 170 | // const position = this.getPositionFromAngle(previous, rad); 171 | // this.state.lastPosition = position.slice() as [number, number]; 172 | 173 | // this.state.cursor.setStart( 174 | // this.state.lastPosition[0], 175 | // this.state.lastPosition[1] 176 | // ); 177 | // const positionDiff = [ 178 | // position[0] - previous[0], 179 | // position[1] - previous[1], 180 | // ]; 181 | // this.setPosition( 182 | // this.activeLine, 183 | // this.lines[this.activeLine].length - 1, 184 | // position 185 | // ); 186 | // this.setChars(); 187 | // this.updatePositions(); 188 | 189 | // this.state.camera.position.set( 190 | // this.state.camera.position.x + positionDiff[0], 191 | // this.state.camera.position.y + positionDiff[1], 192 | // this.state.camera.position.z 193 | // ); 194 | 195 | // this.state.cursor.setEnd( 196 | // this.state.lastPosition[0] + this.state.vector.x, 197 | // this.state.lastPosition[1] + this.state.vector.y 198 | // ); 199 | // } 200 | // } 201 | 202 | // setPosition( 203 | // lineIndex: number, 204 | // index: number, 205 | // position: [number, number] 206 | // ): void { 207 | // let point = 0; 208 | // for (let i = 0; i < lineIndex; i++) { 209 | // index += this.lines[i].length; 210 | // } 211 | // point += index; 212 | // this.positions[point] = position; 213 | // } 214 | 215 | // backspace() { 216 | // if (!this.state.dragging) { 217 | // const line = this.lines[this.activeLine]; 218 | // if (line.length > 0) { 219 | // const previous = this.state.lastPosition.slice(); 220 | // const length = line.length; 221 | // this.lines[this.activeLine] = line.substring(0, length - 1); 222 | // this.setPosition(this.activeLine, length - 1, [0, 0]); 223 | // if (length === 1) { 224 | // this.state.lastPosition = [0, 0]; 225 | // } else { 226 | // this.state.lastPosition = this.positions[length - 2].slice() as [ 227 | // number, 228 | // number 229 | // ]; 230 | // } 231 | // this.setChars(); 232 | // this.updatePositions(); 233 | 234 | // const positionDiff = [ 235 | // this.state.lastPosition[0] - previous[0], 236 | // this.state.lastPosition[1] - previous[1], 237 | // ]; 238 | 239 | // this.state.camera.position.set( 240 | // this.state.camera.position.x + positionDiff[0], 241 | // this.state.camera.position.y + positionDiff[1], 242 | // this.state.camera.position.z 243 | // ); 244 | 245 | // this.state.cursor.setStart( 246 | // this.state.lastPosition[0], 247 | // this.state.lastPosition[1] 248 | // ); 249 | // this.state.cursor.setEnd( 250 | // this.state.lastPosition[0] + this.state.vector.x, 251 | // this.state.lastPosition[1] + this.state.vector.y 252 | // ); 253 | // } 254 | // } 255 | // } 256 | 257 | // enter() { 258 | // this.lines.splice(this.activeLine + 1, 0, ""); 259 | // this.activeLine += 1; 260 | // this.lineStarts = [ 261 | // ...this.lineStarts.slice(0, this.activeLine + 1), 262 | // [0, 1], 263 | // ...this.lineStarts.slice(this.activeLine + 1), 264 | // ]; 265 | 266 | // this.setPosition(this.activeLine, 0, [0, 0]); 267 | // console.log(this.lineStarts); 268 | // } 269 | 270 | // updatePositions() { 271 | // const matrix = new THREE.Matrix4(); 272 | // const euler = new Euler(0, 0, 0); 273 | // let prev = [0, 0]; 274 | // let charCounter = 0; 275 | // for (let j = 0; j < this.lines.length; j++) { 276 | // const line = this.lines[j]; 277 | // const start = this.lineStarts[j]; 278 | // for (let k = 0; k < line.length; k++) { 279 | // const position = this.positions[charCounter]; 280 | // const x = position[0]; 281 | // const y = position[1]; 282 | // const rad = Math.atan2(x - prev[0], prev[1] - y); 283 | // euler.z = rad - Math.PI / 2; 284 | // matrix.makeRotationFromEuler(euler); 285 | // matrix.setPosition(x + start[0], y + start[1], 0); 286 | // this.setMatrixAt(charCounter, matrix); 287 | // if (k === 0) { 288 | // prev = [0, 0]; 289 | // } else { 290 | // prev = [x, y]; 291 | // 292 | // } 293 | // charCounter++; 294 | // } 295 | // console.log(line); 296 | // } 297 | // this.instanceMatrix.needsUpdate = true; 298 | // } 299 | // } 300 | 301 | // export default ArchaicText; 302 | 303 | export {}; 304 | -------------------------------------------------------------------------------- /src/Constants.tsx: -------------------------------------------------------------------------------- 1 | export const BACKGROUND_COLOR = "#ffffff"; 2 | export const TEXT_COLOR = "#000000"; 3 | export const TRANSPARENT = false; 4 | export const SAVE2X = true; 5 | -------------------------------------------------------------------------------- /src/Cursor.tsx: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { MeshBasicMaterial } from "three"; 3 | import { updateVector } from "./Actions"; 4 | import State from "./State"; 5 | 6 | export class Cursor extends THREE.Line { 7 | state: State; 8 | nextMarker: THREE.Mesh; 9 | curMarker: THREE.Mesh; 10 | mouse: THREE.Mesh; 11 | 12 | constructor(state: State) { 13 | const material = new THREE.LineBasicMaterial({ 14 | color: 0x00ff00, 15 | linewidth: 2, 16 | }); 17 | const points = []; 18 | points.push(new THREE.Vector3(0, 0, 0)); 19 | points.push(new THREE.Vector3(60, 0, 0)); 20 | const geometry = new THREE.BufferGeometry().setFromPoints(points); 21 | super(geometry, material); 22 | this.state = state; 23 | this.visible = true; 24 | 25 | { 26 | const geometry = new THREE.CircleGeometry(); 27 | const material = new THREE.MeshBasicMaterial({ color: 0xffff00 }); 28 | this.mouse = new THREE.Mesh(geometry, material); 29 | this.mouse.scale.x = 0.4; 30 | this.mouse.scale.y = 0.4; 31 | state.scene.add(this.mouse); 32 | } 33 | 34 | { 35 | const geometry = new THREE.PlaneGeometry(); 36 | const material = new THREE.MeshBasicMaterial({ color: 0xff00ff }); 37 | this.nextMarker = new THREE.Mesh(geometry, material); 38 | this.nextMarker.scale.x = 0.25; 39 | this.nextMarker.scale.y = 0.5; 40 | state.scene.add(this.nextMarker); 41 | } 42 | 43 | { 44 | const geometry = new THREE.CircleGeometry(); 45 | const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); 46 | this.curMarker = new THREE.Mesh(geometry, material); 47 | this.curMarker.scale.x = 0.08; 48 | this.curMarker.scale.y = 0.08; 49 | state.scene.add(this.curMarker); 50 | } 51 | } 52 | 53 | setStart(x: number, y: number) { 54 | const positions = this.state.cursor.geometry.attributes.position.array; 55 | // @ts-ignore 56 | positions[0] = x; 57 | // @ts-ignore 58 | positions[1] = y; 59 | this.state.cursor.geometry.attributes.position.needsUpdate = true; 60 | 61 | this.curMarker.position.x = x; 62 | this.curMarker.position.y = y; 63 | 64 | this.updateMarker(); 65 | } 66 | 67 | updateMarker() { 68 | const start = this.state.text.linePositions[this.state.text.activeLine]; 69 | const rad = Math.atan2(this.state.vector.y, this.state.vector.x); 70 | const position = this.state.text.getPositionFromAngle( 71 | [ 72 | this.state.lastPosition[0] + start[0], 73 | this.state.lastPosition[1] + start[1], 74 | ], 75 | rad 76 | ); 77 | this.nextMarker.position.set(position[0], position[1], 0); 78 | this.nextMarker.rotation.z = rad; 79 | 80 | const positions = this.state.cursor.geometry.attributes.position.array; 81 | // @ts-ignore 82 | positions[0] = this.nextMarker.position.x; 83 | // @ts-ignore 84 | positions[1] = this.nextMarker.position.y; 85 | this.state.cursor.geometry.attributes.position.needsUpdate = true; 86 | 87 | const linePosition = 88 | this.state.text.linePositions[this.state.text.activeLine]; 89 | this.curMarker.position.set(linePosition[0], linePosition[1], 0); 90 | 91 | const mouseMaterial = this.mouse.material as MeshBasicMaterial; 92 | if (this.state.mode === "choosePosition") { 93 | this.nextMarker.visible = false; 94 | this.visible = false; 95 | mouseMaterial.color.setHex(0x00ff00); 96 | this.mouse.scale.x = 0.4; 97 | this.mouse.scale.y = 0.4; 98 | this.curMarker.visible = false; 99 | } else if (this.state.mode === "navigation") { 100 | mouseMaterial.color.setHex(0x00ffff); 101 | this.visible = false; 102 | this.nextMarker.visible = false; 103 | this.curMarker.visible = false; 104 | } else { 105 | this.nextMarker.visible = true; 106 | this.visible = true; 107 | mouseMaterial.color.setHex(0xffff00); 108 | this.mouse.scale.x = 0.4; 109 | this.mouse.scale.y = 0.4; 110 | this.curMarker.visible = true; 111 | } 112 | } 113 | 114 | updateEndAndCursor() { 115 | updateVector(this.state); 116 | const start = this.state.text.linePositions[this.state.text.activeLine]; 117 | this.state.cursor.setEnd( 118 | this.state.lastPosition[0] + this.state.vector.x + start[0], 119 | this.state.lastPosition[1] + this.state.vector.y + start[1] 120 | ); 121 | } 122 | 123 | setEnd(x: number, y: number) { 124 | const positions = this.state.cursor.geometry.attributes.position.array; 125 | // @ts-ignore 126 | positions[3] = x; 127 | // @ts-ignore 128 | positions[4] = y; 129 | this.state.cursor.geometry.attributes.position.needsUpdate = true; 130 | this.updateMarker(); 131 | 132 | this.mouse.position.set(x, y, 0); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Hud.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useRef } from "react"; 2 | import State from "./State"; 3 | 4 | interface DialogProps { 5 | title: string; 6 | children: any; 7 | display: boolean; 8 | setDisplay: any; 9 | clearModals: any; 10 | } 11 | 12 | function Dialog({ 13 | title, 14 | children, 15 | display, 16 | setDisplay, 17 | clearModals, 18 | }: DialogProps) { 19 | const [offsetX, setOffSetX] = useState(0); 20 | const [offsetY, setOffSetY] = useState(0); 21 | const pointerDown = useRef(false); 22 | const pointerOrigin = useRef([0, 0]); 23 | const offsetOrigin = useRef([0, 0]); 24 | 25 | useEffect(() => { 26 | const downHandler = (e: KeyboardEvent) => { 27 | let press = e.key.toLowerCase(); 28 | if (!e.ctrlKey) { 29 | if (press === "escape") setDisplay(false); 30 | } 31 | }; 32 | 33 | window.addEventListener("keydown", downHandler); 34 | return () => { 35 | window.removeEventListener("keydown", downHandler); 36 | }; 37 | }, [display, clearModals, setDisplay]); 38 | 39 | return ( 40 |
44 |
56 |
57 |
{ 60 | pointerDown.current = true; 61 | pointerOrigin.current = [e.clientX, e.clientY]; 62 | offsetOrigin.current = [offsetX, offsetY]; 63 | }} 64 | onPointerMove={(e) => { 65 | if (pointerDown.current) { 66 | setOffSetX( 67 | offsetOrigin.current[0] + e.clientX - pointerOrigin.current[0] 68 | ); 69 | setOffSetY( 70 | offsetOrigin.current[1] + e.clientY - pointerOrigin.current[1] 71 | ); 72 | } 73 | }} 74 | onPointerUp={() => { 75 | pointerDown.current = false; 76 | }} 77 | > 78 | {title} 79 |
80 |
setDisplay(false)} 84 | > 85 | X 86 |
87 |
88 |
{children}
89 |
90 |
91 | ); 92 | } 93 | 94 | function Hud({ 95 | state, 96 | settingsOpen, 97 | setSettingsOpen, 98 | aboutOpen, 99 | setAboutOpen, 100 | backgroundColor, 101 | setBackgroundColor, 102 | textColor, 103 | setTextColor, 104 | transparentBackground, 105 | setTransparentBackground, 106 | save2x, 107 | setSave2x, 108 | }: { 109 | state: State; 110 | settingsOpen: boolean; 111 | setSettingsOpen: any; 112 | aboutOpen: boolean; 113 | setAboutOpen: any; 114 | backgroundColor: string; 115 | setBackgroundColor: any; 116 | textColor: string; 117 | setTextColor: any; 118 | transparentBackground: boolean; 119 | setTransparentBackground: any; 120 | save2x: boolean; 121 | setSave2x: any; 122 | }) { 123 | const actions = [ 124 | () => { 125 | clearModals(); 126 | setSettingsOpen(!settingsOpen); 127 | }, 128 | () => { 129 | clearModals(); 130 | setAboutOpen(!aboutOpen); 131 | }, 132 | () => { 133 | state.printImage(); 134 | }, 135 | () => { 136 | if (state.mode === "normal" || state.mode === "choosePosition") { 137 | state.setMode("navigation"); 138 | } else if (state.mode === "navigation") { 139 | state.setMode("normal"); 140 | } 141 | }, 142 | ]; 143 | 144 | const clearModals = () => { 145 | setSettingsOpen(false); 146 | setAboutOpen(false); 147 | }; 148 | 149 | const buttons = ["settings", "about", "print"]; 150 | if (state.touch) buttons.push("escape"); 151 | 152 | return ( 153 | <> 154 |
164 | {buttons.map((text, i) => { 165 | return ( 166 |
{ 177 | e.stopPropagation(); 178 | actions[i](); 179 | }} 180 | > 181 | {text} 182 |
183 | ); 184 | })} 185 |
186 | 192 |
193 |
Background color
194 | { 198 | setBackgroundColor(e.target.value); 199 | }} 200 | > 201 |
202 |
203 |
Text color
204 | { 208 | setTextColor(e.target.value); 209 | }} 210 | > 211 |
212 |
213 |
Save at 2x resolution
214 | { 218 | setSave2x(e.target.checked); 219 | }} 220 | > 221 |
222 |
223 |
Transparent background on save
224 | { 228 | setTransparentBackground(e.target.checked); 229 | }} 230 | > 231 |
232 |
233 | 239 |
240 |
241 | Type is a directed typing experiment. You choose the direction the 242 | letters should flow. 243 |
244 |
245 |
Controls
246 |
    247 |
  • Use the mouse or arrow keys to set the target
  • 248 |
  • Type to place the letters
  • 249 |
  • Enter to start a new line
  • 250 |
  • 251 | Escape to go into navigation mode where you can select, move and 252 | delete lines. Double click on a line to edit it 253 |
  • 254 |
255 |
256 |
257 | A{" "} 258 | 263 | Constraint Systems 264 | {" "} 265 | project 266 |
267 |
268 |
269 | 270 | ); 271 | } 272 | 273 | export default Hud; 274 | -------------------------------------------------------------------------------- /src/Keyboard.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import { updateVector } from "./Actions"; 3 | import State from "./State"; 4 | 5 | function Keyboard({ state }: { state: State }) { 6 | const keylist = useRef({}); 7 | 8 | useEffect(() => { 9 | const downHandler = (e: KeyboardEvent) => { 10 | const kl = keylist.current; 11 | let press = e.key.toLowerCase(); 12 | kl[press] = true; 13 | if (state.mode === "normal" || state.mode === "choosePosition") { 14 | if (kl.arrowdown) { 15 | state.mouse.y += 16; 16 | state.cursor.updateEndAndCursor(); 17 | state.movedCheck = true; 18 | } 19 | if (kl.arrowup) { 20 | state.mouse.y -= 16; 21 | state.cursor.updateEndAndCursor(); 22 | state.movedCheck = true; 23 | } 24 | if (kl.arrowleft) { 25 | state.mouse.x -= 16; 26 | state.cursor.updateEndAndCursor(); 27 | state.movedCheck = true; 28 | } 29 | if (kl.arrowright) { 30 | state.mouse.x += 16; 31 | state.cursor.updateEndAndCursor(); 32 | state.movedCheck = true; 33 | } 34 | } 35 | if (state.mode === "choosePosition") { 36 | if (press === "enter") { 37 | const start = state.text.linePositions[state.text.activeLine]; 38 | const newStart = [ 39 | state.lastPosition[0] + state.vector.x + start[0] - 0.01, 40 | state.lastPosition[1] + state.vector.y + start[1], 41 | ] as [number, number]; 42 | state.text.activeLine++; 43 | state.text.lines.push(""); 44 | state.text.linePositions.push(newStart); 45 | state.text.relPositions.push([]); 46 | 47 | state.lastPosition = [0, 0]; 48 | state.text.updatePositions(); 49 | 50 | state.setMode("normal"); 51 | 52 | updateVector(state); 53 | state.cursor.setEnd( 54 | state.lastPosition[0] + state.vector.x + newStart[0], 55 | state.lastPosition[1] + state.vector.y + newStart[1] 56 | ); 57 | } else if (press === "escape") { 58 | state.setMode("navigation"); 59 | state.text.selectedLines = []; 60 | state.text.renderLinesSelected(); 61 | } 62 | } else if (state.mode === "navigation") { 63 | if (press === "backspace") { 64 | const sorted = state.text.selectedLines.slice().sort(function (a, b) { 65 | return a - b; 66 | }); 67 | let adjuster = 0; 68 | for (const index of sorted) { 69 | state.text.lines.splice(index - adjuster, 1); 70 | state.text.relPositions.splice(index - adjuster, 1); 71 | state.text.linePositions.splice(index - adjuster, 1); 72 | state.text.setChars(); 73 | state.text.updatePositions(); 74 | 75 | const selectedBuffer = 76 | state.text.geometry.attributes.selected.array; 77 | // @ts-ignore 78 | selectedBuffer.fill(0); 79 | state.text.geometry.attributes.selected.needsUpdate = true; 80 | 81 | state.text.activeLine = Math.max(0, state.text.activeLine - 1); 82 | const start = state.text.linePositions[state.text.activeLine] || [ 83 | 0, 0, 84 | ]; 85 | updateVector(state); 86 | state.cursor.setEnd( 87 | state.lastPosition[0] + state.vector.x + start[0], 88 | state.lastPosition[1] + state.vector.y + start[1] 89 | ); 90 | 91 | adjuster++; 92 | } 93 | } else if (press === "escape") { 94 | state.text.selectedLines = []; 95 | state.text.renderLinesSelected(); 96 | state.setMode("normal"); 97 | } 98 | } else if (state.mode === "normal") { 99 | if (press.length === 1) { 100 | state.text.addText(e.key); 101 | } else { 102 | if (press === "backspace") { 103 | state.text.backspace(); 104 | } else if (press === "enter") { 105 | state.text.enter(); 106 | } else if (press === "escape") { 107 | state.setMode("navigation"); 108 | } 109 | } 110 | } 111 | }; 112 | 113 | const upHandler = (e: KeyboardEvent) => { 114 | const kl = keylist.current; 115 | let press = e.key.toLowerCase(); 116 | kl[press] = false; 117 | }; 118 | 119 | window.addEventListener("keydown", downHandler); 120 | window.addEventListener("keyup", upHandler); 121 | return () => { 122 | window.removeEventListener("keydown", downHandler); 123 | window.removeEventListener("keyup", upHandler); 124 | }; 125 | }, [state]); 126 | 127 | return null; 128 | } 129 | 130 | export default Keyboard; 131 | -------------------------------------------------------------------------------- /src/LineHandles.tsx: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import State from "./State"; 3 | 4 | const LIMIT = 1000; 5 | class LineHandles extends THREE.InstancedMesh { 6 | state: State; 7 | visibles: Array; 8 | positions: Array<[number, number]>; 9 | 10 | constructor(state: State) { 11 | const geometry = new THREE.CircleGeometry(); 12 | const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); 13 | 14 | super(geometry, material, LIMIT); 15 | state.scene.add(this); 16 | 17 | this.state = state; 18 | 19 | this.visibles = new Array(LIMIT).fill(0); 20 | this.positions = new Array(LIMIT).fill([0, 0]); 21 | 22 | this.updatePositions(); 23 | } 24 | 25 | setPosition(index: number, position: [number, number]) { 26 | this.positions[index] = position; 27 | this.updatePositions(); 28 | } 29 | 30 | updateSelections() { 31 | this.visibles.fill(0); 32 | if (this.state.text) { 33 | if (this.state.text.selectedLines.length > 0) { 34 | for (const index of this.state.text.selectedLines) { 35 | this.visibles[index] = 1; 36 | } 37 | } 38 | if (this.state.text.activeLine !== null) { 39 | this.visibles[this.state.text.activeLine] = 1; 40 | } 41 | } 42 | const matrix = new THREE.Matrix4(); 43 | for (let i = 0; i < LIMIT; i++) { 44 | if (this.visibles[i] === 1) { 45 | const position = this.positions[i]; 46 | matrix.makeScale(0.0, 0.0, 1); 47 | matrix.setPosition(position[0], position[1], 0); 48 | this.setMatrixAt(i, matrix); 49 | } else { 50 | matrix.makeScale(0.0, 0.0, 1); 51 | this.setMatrixAt(i, matrix); 52 | } 53 | } 54 | this.instanceMatrix.needsUpdate = true; 55 | } 56 | 57 | updatePositions() { 58 | const matrix = new THREE.Matrix4(); 59 | for (let i = 0; i < LIMIT; i++) { 60 | const position = this.positions[i]; 61 | matrix.makeScale(0.08, 0.08, 1); 62 | matrix.setPosition(position[0], position[1], 0); 63 | this.setMatrixAt(i, matrix); 64 | } 65 | this.updateSelections(); 66 | this.instanceMatrix.needsUpdate = true; 67 | } 68 | } 69 | 70 | export default LineHandles; 71 | -------------------------------------------------------------------------------- /src/OldPointer.tsx: -------------------------------------------------------------------------------- 1 | // import { useEffect } from "react"; 2 | // import State from "./State"; 3 | // import * as THREE from "three"; 4 | 5 | // export class SubPointer { 6 | // state: State; 7 | // id: number; 8 | // current: THREE.Vector2; 9 | 10 | // constructor(state: State, e: PointerEvent) { 11 | // this.state = state; 12 | // this.id = e.pointerId; 13 | // this.current = new THREE.Vector2(e.clientX, e.clientY); 14 | 15 | // // left button 16 | // this.state.pointers.push(this); 17 | // switch (this.state.pointers.length) { 18 | // case 1: 19 | // // start 1 20 | // this.state.PointerOne.start(this.state.pointers.slice(0, 1)); 21 | // break; 22 | // case 2: 23 | // // start 2 24 | // if (Date.now() - state.PointerOne.startTime) { 25 | // // revert selection 26 | // // state.selectedGrid = state.gridCache; 27 | // // renderSelected(state); 28 | // } 29 | // this.state.PointerOne.cancel(); 30 | // this.state.PointerTwo.start(this.state.pointers.slice(0, 2)); 31 | // break; 32 | // default: 33 | // // three or greater start three 34 | // this.state.PointerTwo.end(); 35 | // this.state.PointerThree.start(this.state.pointers.slice(0, 3)); 36 | // } 37 | // } 38 | 39 | // move(e: PointerEvent) { 40 | // this.current.set(e.clientX, e.clientY); 41 | // switch (this.state.pointers.length) { 42 | // case 1: 43 | // this.state.PointerOne.move(); 44 | // break; 45 | // case 2: 46 | // this.state.PointerTwo.move(); 47 | // break; 48 | // default: 49 | // this.state.PointerThree.move(); 50 | // break; 51 | // } 52 | // } 53 | 54 | // remove() { 55 | // const index = getPointerIndexById(this.state, this.id); 56 | // if (index !== -1) { 57 | // this.state.pointers.splice(index, 1); 58 | // } 59 | // switch (this.state.pointers.length) { 60 | // case 0: 61 | // this.state.PointerOne.end(); 62 | // break; 63 | // case 1: 64 | // // down to 1 65 | // this.state.PointerTwo.end(); 66 | // // do not step down to 1 67 | // // this.state.PointerOne.start(this.state.pointers.slice(0, 1)); 68 | // break; 69 | // case 2: 70 | // // down to 2 71 | // this.state.PointerThree.end(); 72 | // this.state.PointerTwo.start(this.state.pointers.slice(0, 2)); 73 | // break; 74 | // default: 75 | // } 76 | // } 77 | // } 78 | 79 | // type PointerProps = { 80 | // state: State; 81 | // }; 82 | 83 | // const PointerComponent = ({ state }: PointerProps) => { 84 | // useEffect(() => { 85 | // const { canvas } = state; 86 | 87 | // const handlePointerMove = (e: PointerEvent) => { 88 | // if (state.lastPointerButtonPressed === 0) { 89 | // if (state.pointers.length > 0) { 90 | // let pointer; 91 | // if (state.pressed.includes(" ")) { 92 | // pointer = getPointerById(state, 999); 93 | // } else { 94 | // pointer = getPointerById(state, e.pointerId); 95 | // } 96 | // if (pointer) { 97 | // pointer.move(e); 98 | // } 99 | // } else { 100 | // if (state.PointerHover.active) { 101 | // state.PointerHover.move(e.clientX, e.clientY); 102 | // } else { 103 | // state.PointerHover.start(e.clientX, e.clientY); 104 | // } 105 | // } 106 | // } else if (state.lastPointerButtonPressed === 1) { 107 | // if (state.PointerMiddle.active) { 108 | // state.PointerMiddle.move(e); 109 | // } 110 | // } else if (state.lastPointerButtonPressed === 2) { 111 | // // right 112 | // } 113 | // }; 114 | 115 | // const handlePointerDown = (e: PointerEvent) => { 116 | // state.PointerHover.end(); 117 | // state.lastPointerButtonPressed = e.button; 118 | // if (state.lastPointerButtonPressed === 0) { 119 | // new SubPointer(state, e); 120 | // canvas.setPointerCapture(e.pointerId); 121 | // } else if (state.lastPointerButtonPressed === 1) { 122 | // state.PointerMiddle.start(e); 123 | // } else if (state.lastPointerButtonPressed === 2) { 124 | // // right 125 | // } 126 | // canvas.setPointerCapture(e.pointerId); 127 | // }; 128 | 129 | // const handlePointerUp = (e: PointerEvent) => { 130 | // if (state.lastPointerButtonPressed === 0) { 131 | // if (state.pointers.length > 0) { 132 | // const pointer = getPointerById(state, e.pointerId); 133 | // if (pointer) pointer.remove(); 134 | // } else { 135 | // state.PointerHover.end(); 136 | // } 137 | // canvas.releasePointerCapture(e.pointerId); 138 | // } else if (state.lastPointerButtonPressed === 1) { 139 | // state.PointerMiddle.end(); 140 | // state.lastPointerButtonPressed = 0; 141 | // } else if (state.lastPointerButtonPressed === 2) { 142 | // // right 143 | // } 144 | // }; 145 | 146 | // const handleMousewheel = (e: WheelEvent) => { 147 | // e.preventDefault(); 148 | // // discreteZoom(state, e.deltaY); 149 | // }; 150 | 151 | // if (canvas) { 152 | // canvas.addEventListener("pointerdown", handlePointerDown); 153 | // canvas.addEventListener("pointermove", handlePointerMove); 154 | // canvas.addEventListener("pointerup", handlePointerUp); 155 | // canvas.addEventListener("pointercancel", handlePointerUp); 156 | // canvas.addEventListener("wheel", handleMousewheel, { 157 | // passive: false, 158 | // }); 159 | // return () => { 160 | // canvas.removeEventListener("pointerdown", handlePointerDown); 161 | // canvas.removeEventListener("pointermove", handlePointerMove); 162 | // canvas.removeEventListener("pointerup", handlePointerUp); 163 | // canvas.removeEventListener("pointercancel", handlePointerUp); 164 | // canvas.removeEventListener("wheel", handleMousewheel); 165 | // }; 166 | // } 167 | // }, [state]); 168 | 169 | // return null; 170 | // }; 171 | 172 | // export default PointerComponent; 173 | 174 | // const getPointerIndexById = (state: State, pointerId: number) => { 175 | // const ids = state.pointers.map((pointer) => pointer.id); 176 | // return ids.indexOf(pointerId); 177 | // }; 178 | 179 | // const getPointerById = (state: State, pointerId: number) => { 180 | // const index = getPointerIndexById(state, pointerId); 181 | // if (index > -1) { 182 | // return state.pointers[index]; 183 | // } else { 184 | // return null; 185 | // } 186 | // }; 187 | 188 | export default {}; 189 | -------------------------------------------------------------------------------- /src/Pointer.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import State from "./State"; 3 | import * as THREE from "three"; 4 | import { 5 | choosePosition, 6 | moveCamera, 7 | navigationClick, 8 | updateTarget, 9 | moveLines, 10 | targetZoom, 11 | } from "./PointerUtils"; 12 | 13 | const PointerComponent = ({ 14 | state, 15 | keyboardRef, 16 | }: { 17 | state: State; 18 | keyboardRef: any; 19 | }) => { 20 | const pointersRef = useRef([]); 21 | const cameraDown = useRef(new THREE.Vector3()); 22 | const mouse2 = new THREE.Vector2(); 23 | const positionCache = useRef<[number, number][]>([]); 24 | const raycaster = new THREE.Raycaster(); 25 | const clickTime = useRef<{ id: number | null; time: number }>({ 26 | id: null, 27 | time: Date.now(), 28 | }); 29 | 30 | useEffect(() => { 31 | const { canvas } = state; 32 | const activePointers = pointersRef.current; 33 | 34 | const handlePointerMove = (e: PointerEvent) => { 35 | if (activePointers.length === 0) { 36 | hoverMove(e); 37 | } else { 38 | const activeIds = activePointers.map((p) => p.id); 39 | const index = activeIds.indexOf(e.pointerId); 40 | if (index > -1) { 41 | const active = activePointers[index]; 42 | active.current = [e.clientX, e.clientY]; 43 | if (activePointers.length === 1) { 44 | oneDrag(e); 45 | } else if (activePointers.length === 2) { 46 | twoDrag(e); 47 | } 48 | } 49 | } 50 | }; 51 | 52 | const handleMouseDown = (e: MouseEvent) => { 53 | // necessary to preserve focus on mobile evidently 54 | e.preventDefault(); 55 | }; 56 | 57 | const handlePointerDown = (e: PointerEvent) => { 58 | e.preventDefault(); 59 | 60 | if (e.pointerType === "touch") { 61 | if (state.mode !== "navigation") { 62 | keyboardRef.current?.focus(); 63 | } 64 | } 65 | 66 | const prevLength = activePointers.length; 67 | const activeIds = activePointers.map((p) => p.id); 68 | if (activeIds.indexOf(e.pointerId) === -1) { 69 | // limit to 2 70 | if (activePointers.length < 2) { 71 | activePointers.push({ 72 | id: e.pointerId, 73 | down: [e.clientX, e.clientY], 74 | current: [e.clientX, e.clientY], 75 | }); 76 | } 77 | } 78 | const activeLength = activePointers.length; 79 | 80 | if (prevLength === 0 && activeLength === 1) { 81 | oneDragStart(e); 82 | } else if (prevLength === 2 && activeLength === 1) { 83 | oneDragStart(e); 84 | } else if (prevLength === 1 && activeLength === 2) { 85 | twoDragStart(e); 86 | } 87 | 88 | canvas.setPointerCapture(e.pointerId); 89 | }; 90 | 91 | const handleMousewheel = (e: WheelEvent) => { 92 | e.preventDefault(); 93 | cameraDown.current.copy(state.camera.position); 94 | const percent = (window.innerHeight - e.deltaY * 2) / window.innerHeight; 95 | targetZoom(state, [e.clientX, e.clientY], cameraDown.current, percent); 96 | cameraDown.current.copy(state.camera.position); 97 | }; 98 | 99 | const handlePointerUp = (e: PointerEvent) => { 100 | e.preventDefault(); 101 | 102 | const prevLength = activePointers.length; 103 | const activeIds = activePointers.map((p) => p.id); 104 | if (activeIds.indexOf(e.pointerId) !== -1) { 105 | const index = activeIds.indexOf(e.pointerId); 106 | activePointers.splice(index, 1); 107 | } 108 | const activeLength = activePointers.length; 109 | 110 | if (prevLength === 1 && activeLength === 0) { 111 | oneDragEnd(e); 112 | } else if (prevLength === 2 && activeLength === 1) { 113 | twoDragEnd(e); 114 | oneDragStart(e); 115 | } 116 | 117 | canvas.releasePointerCapture(e.pointerId); 118 | }; 119 | 120 | const handleClick = (e: any) => { 121 | e.preventDefault(); 122 | }; 123 | 124 | const hoverMove = (e: PointerEvent) => { 125 | updateTarget(state, e); 126 | }; 127 | 128 | const oneDragStart = (e: PointerEvent) => { 129 | let doubleClick = false; 130 | if (Date.now() - clickTime.current.time < 500) { 131 | doubleClick = true; 132 | } else { 133 | clickTime.current.id = e.pointerId; 134 | clickTime.current.time = Date.now(); 135 | } 136 | 137 | // reset down for all active pointers 138 | for (const active of activePointers) { 139 | active.down = active.current.slice(); 140 | } 141 | 142 | if (e.pointerType === "touch") { 143 | if (state.mode === "choosePosition" && e.button === 0) { 144 | choosePosition(state, e); 145 | } else if (state.mode === "navigation") { 146 | updateTarget(state, e); 147 | navigationClick( 148 | state, 149 | e, 150 | doubleClick, 151 | mouse2, 152 | raycaster, 153 | positionCache, 154 | cameraDown 155 | ); 156 | return; 157 | } else { 158 | state.draggingCamera = false; 159 | } 160 | } else { 161 | if (state.mode === "choosePosition" && e.button === 0) { 162 | choosePosition(state, e); 163 | } else if (state.mode === "navigation") { 164 | navigationClick( 165 | state, 166 | e, 167 | doubleClick, 168 | mouse2, 169 | raycaster, 170 | positionCache, 171 | cameraDown 172 | ); 173 | } 174 | state.draggingCamera = true; 175 | cameraDown.current.copy(state.camera.position); 176 | } 177 | }; 178 | const oneDrag = (e: PointerEvent) => { 179 | const active = activePointers[0]; 180 | 181 | if (e.pointerType === "touch") { 182 | if (state.draggingLine) { 183 | moveLines(state, e, active, positionCache); 184 | } else if (state.draggingCamera) { 185 | moveCamera(state, active, cameraDown); 186 | updateTarget(state, e); 187 | } else { 188 | updateTarget(state, e); 189 | } 190 | } else { 191 | if (state.draggingLine) { 192 | moveLines(state, e, active, positionCache); 193 | } else if (state.draggingCamera) { 194 | moveCamera(state, active, cameraDown); 195 | } 196 | } 197 | }; 198 | const oneDragEnd = (e: PointerEvent) => {}; 199 | 200 | const twoDragStart = (e: PointerEvent) => { 201 | // reset down for all active pointers 202 | for (const active of activePointers) { 203 | active.down = active.current.slice(); 204 | } 205 | state.draggingCamera = true; 206 | cameraDown.current.copy(state.camera.position); 207 | }; 208 | const twoDrag = (e: PointerEvent) => { 209 | const a = activePointers[0]; 210 | const b = activePointers[1]; 211 | const minDown = [ 212 | Math.min(a.down[0], b.down[0]), 213 | Math.min(a.down[1], b.down[1]), 214 | ]; 215 | const maxDown = [ 216 | Math.max(a.down[0], b.down[0]), 217 | Math.max(a.down[1], b.down[1]), 218 | ]; 219 | const min = [ 220 | Math.min(a.current[0], b.current[0]), 221 | Math.min(a.current[1], b.current[1]), 222 | ]; 223 | const max = [ 224 | Math.max(a.current[0], b.current[0]), 225 | Math.max(a.current[1], b.current[1]), 226 | ]; 227 | const combined = { 228 | down: [ 229 | minDown[0] + (maxDown[0] - minDown[0]) / 2, 230 | minDown[1] + (maxDown[1] - minDown[1]) / 2, 231 | ], 232 | current: [ 233 | min[0] + (max[0] - min[0]) / 2, 234 | min[1] + (max[1] - min[1]) / 2, 235 | ], 236 | }; 237 | 238 | const change = moveCamera(state, combined, cameraDown); 239 | const adjustedDown = new THREE.Vector3(); 240 | adjustedDown.x = cameraDown.current.x + change[0]; 241 | adjustedDown.y = cameraDown.current.y + change[1]; 242 | adjustedDown.z = cameraDown.current.z; 243 | const downDiff = Math.sqrt( 244 | Math.pow(b.down[0] - a.down[0], 2) + Math.pow(b.down[1] - a.down[1], 2) 245 | ); 246 | const currDiff = Math.sqrt( 247 | Math.pow(b.current[0] - a.current[0], 2) + 248 | Math.pow(b.current[1] - a.current[1], 2) 249 | ); 250 | const percent = (currDiff - downDiff) / downDiff + 1; 251 | targetZoom( 252 | state, 253 | combined.current as [number, number], 254 | adjustedDown, 255 | percent 256 | ); 257 | }; 258 | const twoDragEnd = (e: PointerEvent) => {}; 259 | 260 | if (canvas) { 261 | canvas.addEventListener("pointerdown", handlePointerDown); 262 | document.addEventListener("pointermove", handlePointerMove); 263 | canvas.addEventListener("mousedown", handleMouseDown); 264 | canvas.addEventListener("pointerup", handlePointerUp); 265 | canvas.addEventListener("pointercancel", handlePointerUp); 266 | canvas.addEventListener("click", handleClick); 267 | canvas.addEventListener("wheel", handleMousewheel, { 268 | passive: false, 269 | }); 270 | return () => { 271 | canvas.removeEventListener("pointerdown", handlePointerDown); 272 | document.removeEventListener("pointermove", handlePointerMove); 273 | canvas.removeEventListener("pointerup", handlePointerUp); 274 | canvas.removeEventListener("pointercancel", handlePointerUp); 275 | canvas.removeEventListener("click", handleClick); 276 | canvas.removeEventListener("wheel", handleMousewheel); 277 | }; 278 | } 279 | }, [state, keyboardRef]); 280 | 281 | return null; 282 | }; 283 | 284 | export default PointerComponent; 285 | -------------------------------------------------------------------------------- /src/PointerUtils.tsx: -------------------------------------------------------------------------------- 1 | import { updateVector } from "./Actions"; 2 | import State from "./State"; 3 | import * as THREE from "three"; 4 | 5 | export const updateTarget = (state: State, e: PointerEvent) => { 6 | state.mouse.set(e.clientX, e.clientY); 7 | updateVector(state); 8 | const start = state.text.linePositions[state.text.activeLine]; 9 | state.cursor.setEnd( 10 | state.lastPosition[0] + state.vector.x + start[0], 11 | state.lastPosition[1] + state.vector.y + start[1] 12 | ); 13 | }; 14 | 15 | export const moveCamera = (state: State, active: any, cameraDown: any) => { 16 | const visibleHeight = 17 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * cameraDown.current.z; 18 | const zoomPixel = visibleHeight / window.innerHeight; 19 | const dragged = [ 20 | active.current[0] - active.down[0], 21 | active.current[1] - active.down[1], 22 | ]; 23 | state.camera.position.x = cameraDown.current.x - dragged[0] * zoomPixel; 24 | state.camera.position.y = cameraDown.current.y + dragged[1] * zoomPixel; 25 | return [-dragged[0] * zoomPixel, dragged[1] * zoomPixel]; 26 | }; 27 | 28 | export const choosePosition = (state: State, e: PointerEvent) => { 29 | updateTarget(state, e); 30 | const start = state.text.linePositions[state.text.activeLine]; 31 | const newStart = [ 32 | state.lastPosition[0] + state.vector.x + start[0] - 0.01, 33 | state.lastPosition[1] + state.vector.y + start[1], 34 | ] as [number, number]; 35 | state.text.activeLine = state.text.lines.length; 36 | state.text.lines.push(""); 37 | state.text.linePositions.push(newStart); 38 | state.text.relPositions.push([]); 39 | state.lastPosition = [0, 0]; 40 | state.text.updatePositions(); 41 | state.setMode("normal"); 42 | updateTarget(state, e); 43 | }; 44 | 45 | export const navigationClick = ( 46 | state: State, 47 | e: PointerEvent, 48 | doubleClick: boolean, 49 | mouse2: THREE.Vector2, 50 | raycaster: THREE.Raycaster, 51 | positionCache: any, 52 | cameraDown: any 53 | ) => { 54 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1; 55 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1; 56 | raycaster.setFromCamera(mouse2, state.camera); 57 | const intersects = raycaster.intersectObject(state.text); 58 | if (intersects.length > 0) { 59 | const instanceId = intersects[0].instanceId; 60 | if (instanceId !== null && instanceId !== undefined) { 61 | const lineIndex = state.text.getInstanceLineIndex(instanceId); 62 | if (lineIndex !== undefined) { 63 | if (e.shiftKey) { 64 | state.text.selectedLines.push(lineIndex); 65 | } else { 66 | if (state.text.selectedLines.includes(lineIndex)) { 67 | if (doubleClick) { 68 | state.text.selectedLines = []; 69 | state.text.activeLine = lineIndex; 70 | const relPositions = state.text.relPositions[lineIndex]; 71 | state.lastPosition = relPositions[ 72 | relPositions.length - 1 73 | ].slice() as [number, number]; 74 | state.text.selectedLines = []; 75 | state.setMode("normal"); 76 | } 77 | } else { 78 | state.text.selectedLines = []; 79 | state.text.selectedLines.push(lineIndex); 80 | } 81 | } 82 | } 83 | state.text.renderLinesSelected(); 84 | state.draggingLine = true; 85 | positionCache.current = []; 86 | for (const line of state.text.selectedLines) { 87 | // positionCache.push() 88 | positionCache.current.push( 89 | state.text.linePositions[line].slice() as [number, number] 90 | ); 91 | } 92 | return; 93 | } 94 | } else { 95 | state.draggingLine = false; 96 | if (!e.shiftKey) state.text.selectedLines = []; 97 | state.text.renderLinesSelected(); 98 | 99 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1; 100 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1; 101 | updateTarget(state, e); 102 | 103 | if (doubleClick) { 104 | state.setMode("normal"); 105 | 106 | const start = state.text.linePositions[state.text.activeLine]; 107 | const newStart = [ 108 | state.lastPosition[0] + state.vector.x + start[0] - 0.01, 109 | state.lastPosition[1] + state.vector.y + start[1], 110 | ] as [number, number]; 111 | state.text.activeLine++; 112 | state.text.lines.push(""); 113 | state.text.linePositions.push(newStart); 114 | state.text.relPositions.push([]); 115 | 116 | state.lastPosition = [0, 0]; 117 | state.text.updatePositions(); 118 | 119 | state.setMode("normal"); 120 | } 121 | 122 | state.draggingCamera = true; 123 | cameraDown.current.copy(state.camera.position); 124 | } 125 | }; 126 | 127 | export const moveLines = ( 128 | state: State, 129 | e: PointerEvent, 130 | active: any, 131 | positionCache: any 132 | ) => { 133 | const visibleHeight = 134 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * state.camera.position.z; 135 | const zoomPixel = visibleHeight / window.innerHeight; 136 | 137 | const dragged = [ 138 | active.current[0] - active.down[0], 139 | active.current[1] - active.down[1], 140 | ]; 141 | for (let i = 0; i < positionCache.current.length; i++) { 142 | const index = state.text.selectedLines[i]; 143 | state.text.linePositions[index] = [ 144 | positionCache.current[i][0] + dragged[0] * zoomPixel, 145 | positionCache.current[i][1] - dragged[1] * zoomPixel, 146 | ]; 147 | } 148 | state.text.updatePositions(); 149 | 150 | updateTarget(state, e); 151 | }; 152 | 153 | export const targetZoom = ( 154 | state: State, 155 | target: [number, number], 156 | cameraDown: THREE.Vector3, 157 | percent: number 158 | ) => { 159 | const visibleHeight = 160 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * cameraDown.z; 161 | const zoomPixel = visibleHeight / window.innerHeight; 162 | 163 | const relx = target[0] - window.innerWidth / 2; 164 | const rely = -(target[1] - window.innerHeight / 2); 165 | const worldRelX = relx * zoomPixel; 166 | const worldRelY = rely * zoomPixel; 167 | 168 | const boundZoom = (state: State, val: number) => { 169 | const min = 3; 170 | const max = 18; 171 | return Math.min(max, Math.max(min, val)); 172 | }; 173 | 174 | const nextZoom = boundZoom(state, cameraDown.z / percent); 175 | 176 | const newVisibleHeight = 177 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * nextZoom; 178 | const newZoomPixel = newVisibleHeight / window.innerHeight; 179 | 180 | const newWorldX = relx * newZoomPixel; 181 | const newWorldY = rely * newZoomPixel; 182 | 183 | const diffX = newWorldX - worldRelX; 184 | const diffY = newWorldY - worldRelY; 185 | 186 | state.camera.position.x = cameraDown.x - diffX; 187 | state.camera.position.y = cameraDown.y - diffY; 188 | state.camera.position.z = nextZoom; 189 | }; 190 | -------------------------------------------------------------------------------- /src/RawPointer.tsx: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { useEffect, useState, useRef } from "react"; 3 | import { updateVector, setRay } from "./Actions"; 4 | import State from "./State"; 5 | 6 | function Pointer({ state }: { state: State }) { 7 | const down = useRef<[number, number]>([0, 0]); 8 | const clickTime = useRef(Date.now()); 9 | const cameraDown = useRef(new THREE.Vector3()); 10 | const raycaster = new THREE.Raycaster(); 11 | const mouse2 = new THREE.Vector2(); 12 | const dragCache = new THREE.Vector2(); 13 | const positionCache = useRef<[number, number][]>([]); 14 | 15 | useEffect(() => { 16 | const { canvas } = state; 17 | 18 | function pointerUp(e: PointerEvent) { 19 | state.draggingLine = false; 20 | state.draggingCamera = false; 21 | } 22 | 23 | function pointerDown(e: PointerEvent) { 24 | let doubleClick = false; 25 | if (Date.now() - clickTime.current < 500) { 26 | doubleClick = true; 27 | } else { 28 | clickTime.current = Date.now(); 29 | } 30 | 31 | if (state.mode === "choosePosition" && e.button === 0) { 32 | const start = state.text.linePositions[state.text.activeLine]; 33 | const newStart = [ 34 | state.lastPosition[0] + state.vector.x + start[0] - 0.01, 35 | state.lastPosition[1] + state.vector.y + start[1], 36 | ] as [number, number]; 37 | state.text.activeLine++; 38 | state.text.lines.push(""); 39 | state.text.linePositions.push(newStart); 40 | state.text.relPositions.push([]); 41 | 42 | state.lastPosition = [0, 0]; 43 | state.text.updatePositions(); 44 | 45 | state.setMode("normal"); 46 | 47 | updateVector(state); 48 | state.cursor.setEnd( 49 | state.lastPosition[0] + state.vector.x + newStart[0], 50 | state.lastPosition[1] + state.vector.y + newStart[1] 51 | ); 52 | 53 | state.draggingCamera = true; 54 | 55 | down.current = [e.clientX, e.clientY]; 56 | cameraDown.current.copy(state.camera.position); 57 | } else if (state.mode === "navigation" && e.button === 0) { 58 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1; 59 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1; 60 | raycaster.setFromCamera(mouse2, state.camera); 61 | const intersects = raycaster.intersectObject(state.text); 62 | if (intersects.length > 0) { 63 | const instanceId = intersects[0].instanceId; 64 | if (instanceId !== null && instanceId !== undefined) { 65 | const lineIndex = state.text.getInstanceLineIndex(instanceId); 66 | if (lineIndex !== undefined) { 67 | if (e.shiftKey) { 68 | state.text.selectedLines.push(lineIndex); 69 | } else { 70 | if (state.text.selectedLines.includes(lineIndex)) { 71 | if (doubleClick) { 72 | state.text.selectedLines = []; 73 | state.text.activeLine = lineIndex; 74 | const relPositions = state.text.relPositions[lineIndex]; 75 | state.lastPosition = relPositions[ 76 | relPositions.length - 1 77 | ].slice() as [number, number]; 78 | state.text.selectedLines = []; 79 | state.setMode("normal"); 80 | } 81 | } else { 82 | state.text.selectedLines = []; 83 | state.text.selectedLines.push(lineIndex); 84 | } 85 | } 86 | } 87 | state.text.renderLinesSelected(); 88 | state.draggingLine = true; 89 | positionCache.current = []; 90 | for (const line of state.text.selectedLines) { 91 | // positionCache.push() 92 | positionCache.current.push( 93 | state.text.linePositions[line].slice() as [number, number] 94 | ); 95 | } 96 | down.current = [e.clientX, e.clientY]; 97 | return; 98 | } 99 | } else { 100 | state.draggingCamera = true; 101 | if (!e.shiftKey) state.text.selectedLines = []; 102 | state.text.renderLinesSelected(); 103 | 104 | if (doubleClick) { 105 | state.setMode("normal"); 106 | 107 | const start = state.text.linePositions[state.text.activeLine]; 108 | const newStart = [ 109 | state.lastPosition[0] + state.vector.x + start[0] - 0.01, 110 | state.lastPosition[1] + state.vector.y + start[1], 111 | ] as [number, number]; 112 | state.text.activeLine++; 113 | state.text.lines.push(""); 114 | state.text.linePositions.push(newStart); 115 | state.text.relPositions.push([]); 116 | 117 | state.lastPosition = [0, 0]; 118 | state.text.updatePositions(); 119 | 120 | state.setMode("normal"); 121 | 122 | updateVector(state); 123 | state.cursor.setEnd( 124 | state.lastPosition[0] + state.vector.x + newStart[0], 125 | state.lastPosition[1] + state.vector.y + newStart[1] 126 | ); 127 | 128 | state.draggingCamera = true; 129 | 130 | down.current = [e.clientX, e.clientY]; 131 | cameraDown.current.copy(state.camera.position); 132 | } 133 | } 134 | 135 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1; 136 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1; 137 | state.draggingCamera = true; 138 | 139 | down.current = [e.clientX, e.clientY]; 140 | cameraDown.current.copy(state.camera.position); 141 | } else { 142 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1; 143 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1; 144 | state.draggingCamera = true; 145 | 146 | state.mouse.set(e.clientX, e.clientY); 147 | 148 | updateVector(state); 149 | 150 | const start = state.text.linePositions[state.text.activeLine]; 151 | state.cursor.setEnd( 152 | state.lastPosition[0] + state.vector.x + start[0], 153 | state.lastPosition[1] + state.vector.y + start[1] 154 | ); 155 | 156 | down.current = [e.clientX, e.clientY]; 157 | cameraDown.current.copy(state.camera.position); 158 | } 159 | } 160 | 161 | function pointerMove(e: PointerEvent) { 162 | state.mouse.set(e.clientX, e.clientY); 163 | state.movedCheck = true; 164 | if (state.draggingLine || state.draggingCamera) { 165 | const visibleHeight = 166 | 2 * 167 | Math.tan((state.camera.fov * Math.PI) / 360) * 168 | state.camera.position.z; 169 | const zoomPixel = visibleHeight / window.innerHeight; 170 | 171 | const dragged = [ 172 | state.mouse.x - down.current[0], 173 | state.mouse.y - down.current[1], 174 | ]; 175 | if (state.draggingLine) { 176 | for (let i = 0; i < positionCache.current.length; i++) { 177 | const index = state.text.selectedLines[i]; 178 | state.text.linePositions[index] = [ 179 | positionCache.current[i][0] + dragged[0] * zoomPixel, 180 | positionCache.current[i][1] - dragged[1] * zoomPixel, 181 | ]; 182 | } 183 | state.text.updatePositions(); 184 | 185 | updateVector(state); 186 | const start = state.text.linePositions[state.text.activeLine]; 187 | state.cursor.setEnd( 188 | state.lastPosition[0] + state.vector.x + start[0], 189 | state.lastPosition[1] + state.vector.y + start[1] 190 | ); 191 | } else if (state.draggingCamera) { 192 | state.camera.position.x = 193 | cameraDown.current.x - dragged[0] * zoomPixel; 194 | state.camera.position.y = 195 | cameraDown.current.y + dragged[1] * zoomPixel; 196 | } 197 | } else { 198 | updateVector(state); 199 | 200 | const start = state.text.linePositions[state.text.activeLine]; 201 | state.cursor.setEnd( 202 | state.lastPosition[0] + state.vector.x + start[0], 203 | state.lastPosition[1] + state.vector.y + start[1] 204 | ); 205 | } 206 | } 207 | 208 | const mouseWheel = (e: Event) => { 209 | const deltaY = (e as WheelEvent).deltaY; 210 | const percent = (window.innerHeight + deltaY * 2) / window.innerHeight; 211 | const newZ = Math.min(18, Math.max(3, state.camera.position.z * percent)); 212 | setRay(state, state.ray, state.mouse, newZ); 213 | state.camera.position.copy(state.ray); 214 | }; 215 | 216 | window.document.addEventListener("pointerdown", pointerDown); 217 | window.document.addEventListener("pointermove", pointerMove); 218 | window.document.addEventListener("pointerup", pointerUp); 219 | window.document.addEventListener("mousewheel", mouseWheel, { 220 | passive: false, 221 | }); 222 | return () => { 223 | window.document.removeEventListener("mousewheel", mouseWheel); 224 | window.document.removeEventListener("pointerdown", pointerDown); 225 | window.document.removeEventListener("pointermove", pointerMove); 226 | window.document.removeEventListener("pointerup", pointerUp); 227 | }; 228 | }, [state]); 229 | 230 | return null; 231 | } 232 | 233 | export default Pointer; 234 | -------------------------------------------------------------------------------- /src/Rotators.tsx: -------------------------------------------------------------------------------- 1 | const test = {}; 2 | 3 | export default test; 4 | -------------------------------------------------------------------------------- /src/State.tsx: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { updateVector } from "./Actions"; 3 | import { Cursor } from "./Cursor"; 4 | import { BACKGROUND_COLOR, SAVE2X, TRANSPARENT } from "./Constants"; 5 | import Text from "./Text"; 6 | 7 | class State { 8 | canvas: HTMLCanvasElement; 9 | camera: THREE.PerspectiveCamera; 10 | printCamera: THREE.PerspectiveCamera; 11 | renderer: THREE.WebGLRenderer; 12 | printRenderer: THREE.WebGLRenderer; 13 | scene: THREE.Scene; 14 | text: Text; 15 | data: string; 16 | worldPixel: number; 17 | center: THREE.Vector2; 18 | mouse: THREE.Vector2; 19 | vector: THREE.Vector2; 20 | tempVec: THREE.Vector3; 21 | ray: THREE.Vector3; 22 | lastPosition: [number, number]; 23 | cursor: Cursor; 24 | draggingCamera: boolean; 25 | draggingLine: boolean; 26 | movedCheck: boolean; 27 | mode: "normal" | "choosePosition" | "navigation"; 28 | transparentBackground: boolean; 29 | save2x: boolean; 30 | touch: boolean; 31 | 32 | constructor(canvas: HTMLCanvasElement, printCanvas: HTMLCanvasElement) { 33 | this.canvas = canvas; 34 | this.camera = new THREE.PerspectiveCamera( 35 | 75, 36 | window.innerWidth / window.innerHeight, 37 | 0.1, 38 | 100 39 | ); 40 | this.printCamera = new THREE.PerspectiveCamera( 41 | 75, 42 | window.innerWidth / window.innerHeight, 43 | 0.1, 44 | 100 45 | ); 46 | this.renderer = new THREE.WebGLRenderer({ 47 | canvas, 48 | alpha: true, 49 | }); 50 | this.renderer.setPixelRatio(window.devicePixelRatio); 51 | this.renderer.setSize(window.innerWidth, window.innerHeight); 52 | 53 | this.printRenderer = new THREE.WebGLRenderer({ 54 | canvas: printCanvas, 55 | alpha: true, 56 | }); 57 | this.printRenderer.setPixelRatio(window.devicePixelRatio); 58 | this.printRenderer.setSize(window.innerWidth, window.innerHeight); 59 | 60 | this.scene = new THREE.Scene(); 61 | this.center = new THREE.Vector2( 62 | window.innerWidth / 2, 63 | window.innerHeight / 2 64 | ); 65 | this.vector = new THREE.Vector2(); 66 | 67 | this.draggingLine = false; 68 | this.draggingCamera = false; 69 | 70 | const ZSTART = 10; 71 | // set world pixel 72 | { 73 | const visibleHeight = 74 | 2 * Math.tan((this.camera.fov * Math.PI) / 360) * ZSTART; 75 | this.worldPixel = visibleHeight / window.innerHeight; 76 | } 77 | 78 | this.data = ""; 79 | this.ray = new THREE.Vector3(); 80 | this.tempVec = new THREE.Vector3(); 81 | this.cursor = new Cursor(this); 82 | this.mouse = new THREE.Vector2(window.innerWidth - 48, 72); 83 | this.movedCheck = false; 84 | this.setBackgroundColor(BACKGROUND_COLOR); 85 | this.transparentBackground = TRANSPARENT; 86 | this.save2x = SAVE2X; 87 | 88 | this.lastPosition = [0, 0]; 89 | this.text = new Text(this, [ 90 | (-window.innerWidth / 2 + 24) * this.worldPixel, 91 | (window.innerHeight / 2 - 72) * this.worldPixel, 92 | ]); 93 | this.mode = "normal"; 94 | 95 | this.scene.add(this.cursor); 96 | 97 | this.camera.position.z = ZSTART; 98 | 99 | this.touch = window.matchMedia("(pointer: coarse)").matches; 100 | 101 | updateVector(this); 102 | 103 | const start = this.text.linePositions[this.text.activeLine]; 104 | this.cursor.setEnd( 105 | this.lastPosition[0] + this.vector.x + start[0], 106 | this.lastPosition[1] + this.vector.y + start[1] 107 | ); 108 | 109 | this.animate(); 110 | 111 | const handleResize = () => { 112 | this.renderer.setSize(window.innerWidth, window.innerHeight); 113 | this.camera.aspect = window.innerWidth / window.innerHeight; 114 | this.camera.updateProjectionMatrix(); 115 | }; 116 | window.addEventListener("resize", handleResize); 117 | } 118 | 119 | setMode(newMode: "normal" | "choosePosition" | "navigation") { 120 | this.mode = newMode; 121 | this.cursor.updateMarker(); 122 | } 123 | 124 | setBackgroundColor(color: string) { 125 | this.scene.background = new THREE.Color(color); 126 | } 127 | 128 | animate() { 129 | // this.renderer.setClearColor(0xff0000, 0); 130 | this.renderer.clear(); 131 | this.renderer.render(this.scene, this.camera); 132 | requestAnimationFrame(this.animate.bind(this)); 133 | } 134 | 135 | printImage() { 136 | if (this.text.charCounter > 0) { 137 | let multiplier = 1; 138 | if (this.save2x) multiplier = 2; 139 | 140 | const visibleHeight = 141 | 2 * Math.tan((this.camera.fov * Math.PI) / 360) * 10; 142 | const zoomPixel = visibleHeight / window.innerHeight; 143 | let { top, left, bottom, right } = this.text.getPoints(); 144 | const pad = 0.6; 145 | top += pad; 146 | bottom -= pad; 147 | left -= pad; 148 | right += pad; 149 | const width = ((right - left) / zoomPixel) * multiplier; 150 | const height = ((top - bottom) / zoomPixel) * multiplier; 151 | const center = [left + (right - left) / 2, bottom + (top - bottom) / 2]; 152 | 153 | const adjust = height / window.innerHeight / multiplier; 154 | 155 | this.printCamera.position.x = center[0]; 156 | this.printCamera.position.y = center[1]; 157 | this.printCamera.position.z = 10 * adjust; 158 | 159 | this.printCamera.aspect = width / height; 160 | this.printCamera.updateProjectionMatrix(); 161 | this.printRenderer.setSize(width, height); 162 | 163 | this.cursor.visible = false; 164 | this.cursor.curMarker.visible = false; 165 | this.cursor.nextMarker.visible = false; 166 | this.cursor.mouse.visible = false; 167 | 168 | let cacheBackground = Object.assign({}, this.scene.background); 169 | if (this.transparentBackground) { 170 | this.scene.background = null; 171 | } 172 | this.printRenderer.setClearColor(0x000000, 0); 173 | this.printRenderer.clear(); 174 | this.printRenderer.render(this.scene, this.printCamera); 175 | 176 | this.printRenderer.domElement.toBlob((blob) => { 177 | const link = document.createElement("a"); 178 | link.setAttribute( 179 | "download", 180 | "type-" + Math.round(new Date().getTime() / 1000) + ".png" 181 | ); 182 | link.setAttribute("href", URL.createObjectURL(blob)); 183 | link.dispatchEvent( 184 | new MouseEvent(`click`, { 185 | bubbles: true, 186 | cancelable: true, 187 | view: window, 188 | }) 189 | ); 190 | 191 | if (this.transparentBackground) { 192 | this.scene.background = cacheBackground; 193 | } 194 | 195 | this.cursor.visible = true; 196 | this.cursor.mouse.visible = true; 197 | this.cursor.curMarker.visible = true; 198 | this.cursor.nextMarker.visible = true; 199 | }); 200 | } 201 | } 202 | } 203 | 204 | export default State; 205 | -------------------------------------------------------------------------------- /src/Text.tsx: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { Euler, ShaderMaterial } from "three"; 3 | import { updateVector } from "./Actions"; 4 | import { TEXT_COLOR } from "./Constants"; 5 | import LineHandles from "./LineHandles"; 6 | import State from "./State"; 7 | 8 | const makeCanvas = (c: HTMLCanvasElement, chars: string[], color: string) => { 9 | const cx = c.getContext("2d")!; 10 | cx.clearRect(0, 0, c.width, c.height); 11 | const fs = 64; 12 | cx.font = fs + "px custom"; 13 | 14 | const ch = Math.round(fs * 1.2); 15 | 16 | const toMeasure = cx.measureText("n"); 17 | const cw = toMeasure.width; 18 | c.width = 2048; 19 | const rows = Math.ceil((chars.length * cw) / c.width); 20 | c.height = rows * ch; 21 | const perRow = Math.floor(c.width / cw); 22 | 23 | cx.fillStyle = "white"; 24 | cx.fillRect(0, 0, c.width, c.height); 25 | cx.clearRect(0, 0, c.width, c.height); 26 | 27 | // have to set font again after resize 28 | cx.font = fs + "px custom"; 29 | cx.fillStyle = color; 30 | cx.textBaseline = "middle"; 31 | for (let i = 0; i < chars.length; i++) { 32 | const char = chars[i]; 33 | const col = i % perRow; 34 | const row = Math.floor(i / perRow); 35 | // console.log(col * cw, row * ch + ch / 2); 36 | cx.fillText(char, col * cw, row * ch + ch / 2); 37 | } 38 | return { c, cw, ch, rows, perRow }; 39 | }; 40 | 41 | const LIMIT = 2000; 42 | class Text extends THREE.InstancedMesh { 43 | chars: Array; 44 | state: State; 45 | aspect: number; 46 | lines: Array; 47 | linePositions: Array<[number, number]>; 48 | relPositions: Array>; 49 | activeLine: number; 50 | lineHandles: LineHandles; 51 | dragLineIndex: null | number; 52 | selectedLines: Array; 53 | perRow: number; 54 | rows: number; 55 | canvas: HTMLCanvasElement; 56 | ch: number; 57 | charCounter: number; 58 | 59 | constructor(state: State, lineStart: [number, number]) { 60 | const geometry = new THREE.PlaneBufferGeometry(); 61 | var uv = geometry.getAttribute("uv"); 62 | let texture; 63 | let texScale = [1, 1]; 64 | let aspect; 65 | let rows; 66 | let perRow; 67 | const chars = 68 | " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012346789%$€¥£¢&*@#|áâàäåãæçéêèëíîìï:;-–—•,.…'\"`„‹›«»/\\?!¿¡()[]{}©®§+×=_°~^<>".split( 69 | "" 70 | ); 71 | const canvas = document.createElement("canvas"); 72 | let ch; 73 | { 74 | const madeCanvas = makeCanvas(canvas, chars, TEXT_COLOR); 75 | const c = madeCanvas.c; 76 | const cw = madeCanvas.cw; 77 | ch = madeCanvas.ch; 78 | rows = madeCanvas.rows; 79 | perRow = madeCanvas.perRow; 80 | texture = new THREE.CanvasTexture(c); 81 | 82 | uv.setXY(0, 0, 1); 83 | uv.setXY(1, 1, 1); 84 | uv.setXY(2, 0, 0); 85 | uv.setXY(3, 1, 0); 86 | texScale[0] = cw / c.width; 87 | texScale[1] = ch / c.height; 88 | 89 | aspect = [cw / ch, 1, 1]; 90 | } 91 | 92 | const offsets = []; 93 | for (let i = 0; i < LIMIT; i++) { 94 | offsets.push(0, 0); 95 | } 96 | 97 | const selected = []; 98 | for (let i = 0; i < LIMIT; i++) { 99 | selected.push(0); 100 | } 101 | 102 | geometry.setAttribute( 103 | "offset", 104 | new THREE.InstancedBufferAttribute(new Float32Array(offsets), 2, false) 105 | ); 106 | 107 | geometry.setAttribute( 108 | "selected", 109 | new THREE.InstancedBufferAttribute(new Float32Array(selected), 1, false) 110 | ); 111 | 112 | const vertexShader = ` 113 | varying vec2 vUv; 114 | attribute vec2 offset; 115 | varying vec2 vOffset; 116 | uniform vec2 texScale; 117 | varying vec2 vTexScale; 118 | uniform vec3 aspect; 119 | uniform float scale; 120 | attribute float selected; 121 | varying float vSelected; 122 | 123 | void main() { 124 | vUv = uv * texScale; 125 | vOffset = offset * texScale; 126 | vTexScale = texScale; 127 | vSelected = selected; 128 | 129 | gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position * aspect * scale, 1.0); 130 | } 131 | `; 132 | 133 | const fragmentShader = ` 134 | uniform sampler2D texture1; 135 | varying vec2 vUv; 136 | varying vec2 vOffset; 137 | varying float vSelected; 138 | uniform vec3 color; 139 | 140 | void main() { 141 | vec4 tex = texture2D(texture1, vec2(vUv.x + vOffset.x, vUv.y + vOffset.y )); 142 | vec4 colored = vec4(color, tex.a); 143 | if (vSelected == 1.0 && colored.a < 0.2) { 144 | colored.r = 0.5; 145 | colored.g = 1.0; 146 | colored.b = 0.5; 147 | colored.a = 1.0; 148 | } 149 | gl_FragColor = colored; 150 | } 151 | `; 152 | 153 | var uniforms = { 154 | texture1: { type: "t", value: texture }, 155 | texScale: { value: texScale }, 156 | aspect: { value: aspect }, 157 | selected: { value: selected }, 158 | scale: { value: 0.5 }, 159 | color: { value: [1.0, 0.0, 0.0] }, 160 | }; 161 | 162 | const material = new THREE.ShaderMaterial({ 163 | uniforms: uniforms, 164 | vertexShader: vertexShader, 165 | fragmentShader: fragmentShader, 166 | }); 167 | material.transparent = true; 168 | 169 | super(geometry, material, LIMIT); 170 | this.chars = chars; 171 | this.state = state; 172 | this.aspect = aspect[0]; 173 | this.perRow = perRow; 174 | this.rows = rows; 175 | this.lines = [""]; 176 | this.linePositions = [lineStart.slice() as [number, number]]; 177 | this.relPositions = [[]]; 178 | this.activeLine = 0; 179 | this.dragLineIndex = null; 180 | this.selectedLines = []; 181 | this.canvas = canvas; 182 | this.ch = ch; 183 | this.charCounter = 0; 184 | 185 | state.scene.add(this); 186 | 187 | this.lineHandles = new LineHandles(state); 188 | 189 | // this.setChars(); 190 | 191 | // const startText = "start typing"; 192 | // let counter = 0; 193 | // let interval = setInterval(() => { 194 | // if (counter === startText.length - 1) clearInterval(interval); 195 | // this.addText(startText[counter]); 196 | // counter++; 197 | // }, 30); 198 | 199 | // setInterval(() => { 200 | // this.addText(chars[Math.floor(Math.random() * chars.length)]); 201 | // }, 20); 202 | } 203 | 204 | setColor(color: string) { 205 | (this.material as ShaderMaterial).uniforms.color.value = new THREE.Color( 206 | color 207 | ).toArray(); 208 | } 209 | 210 | getInstanceLineIndex(instanceIndex: number) { 211 | let total = 0; 212 | for (let i = 0; i < this.lines.length; i++) { 213 | const line = this.lines[i]; 214 | const lineLength = line.length; 215 | if (instanceIndex < total + lineLength - 1) return i; 216 | total += lineLength; 217 | } 218 | } 219 | 220 | renderLinesSelected() { 221 | const selectedBuffer = this.geometry.attributes.selected.array; 222 | // @ts-ignore 223 | selectedBuffer.fill(0); 224 | let charCounter = 0; 225 | for (let i = 0; i < this.lines.length; i++) { 226 | const line = this.lines[i]; 227 | if (this.selectedLines.includes(i)) { 228 | for (let j = 0; j < line.length; j++) { 229 | // @ts-ignore 230 | selectedBuffer[charCounter + j] = 1; 231 | } 232 | } 233 | charCounter += line.length; 234 | } 235 | this.geometry.attributes.selected.needsUpdate = true; 236 | } 237 | 238 | getPositionFromAngle( 239 | prev: [number, number], 240 | angle: number 241 | ): [number, number] { 242 | const rx = 0.6 * this.aspect; 243 | const x = prev[0] + Math.cos(angle) * rx; 244 | const y = prev[1] + Math.sin(angle) * rx; 245 | return [x, y]; 246 | } 247 | 248 | getPoints() { 249 | const _center = new THREE.Vector3(); 250 | const _matrix = new THREE.Matrix4(); 251 | const _scale = new THREE.Vector3(); 252 | const _quaternion = new THREE.Quaternion(); 253 | let left = Infinity; 254 | let right = -Infinity; 255 | let top = -Infinity; 256 | let bottom = Infinity; 257 | 258 | const activePoints = this.lines.reduce( 259 | (total, curr) => total + curr.length, 260 | 0 261 | ); 262 | 263 | for (let instanceId = 0; instanceId < activePoints; instanceId++) { 264 | this.getMatrixAt(instanceId, _matrix); 265 | 266 | _matrix.decompose(_center, _quaternion, _scale); 267 | // apply parent transforms to instance 268 | // _center.applyMatrix4(this.matrixWorld); 269 | left = Math.min(_center.x, left); 270 | right = Math.max(_center.x, right); 271 | top = Math.max(_center.y, top); 272 | bottom = Math.min(_center.y, bottom); 273 | } 274 | return { top, left, right, bottom }; 275 | } 276 | 277 | addText(data: string) { 278 | if (this.charCounter < LIMIT) { 279 | const rad = Math.atan2(this.state.vector.y, this.state.vector.x); 280 | const position = this.getPositionFromAngle(this.state.lastPosition, rad); 281 | 282 | const start = this.state.text.linePositions[this.state.text.activeLine]; 283 | const cursor = [ 284 | this.state.lastPosition[0] + this.state.vector.x, 285 | this.state.lastPosition[1] + this.state.vector.y, 286 | ]; 287 | const prevSigns = [ 288 | cursor[0] - this.state.lastPosition[0] < 0 ? -1 : 1, 289 | cursor[1] - this.state.lastPosition[1] < 0 ? -1 : 1, 290 | ]; 291 | const nextSigns = [ 292 | cursor[0] - position[0] < 0 ? -1 : 1, 293 | cursor[1] - position[1] < 0 ? -1 : 1, 294 | ]; 295 | if (prevSigns[0] !== nextSigns[0] || prevSigns[1] !== nextSigns[1]) { 296 | return; 297 | } 298 | 299 | this.lines[this.activeLine] += data; 300 | this.relPositions[this.activeLine].push(position); 301 | this.setChars(); 302 | this.updatePositions(); 303 | 304 | // const positionDiff = [ 305 | // position[0] - this.state.lastPosition[0], 306 | // position[1] - this.state.lastPosition[1], 307 | // ]; 308 | 309 | const relPositions = this.relPositions[this.activeLine]; 310 | this.state.lastPosition = relPositions[ 311 | relPositions.length - 1 312 | ].slice() as [number, number]; 313 | 314 | updateVector(this.state); 315 | 316 | // this.state.camera.position.set( 317 | // this.state.camera.position.x + positionDiff[0], 318 | // this.state.camera.position.y + positionDiff[1], 319 | // this.state.camera.position.z 320 | // ); 321 | 322 | this.state.cursor.setEnd( 323 | this.state.lastPosition[0] + this.state.vector.x + start[0], 324 | this.state.lastPosition[1] + this.state.vector.y + start[1] 325 | ); 326 | 327 | this.state.movedCheck = false; 328 | } else { 329 | alert("You have reached the charater limit of " + 2000 + " characters."); 330 | } 331 | } 332 | 333 | backspace() { 334 | const line = this.lines[this.activeLine]; 335 | if (line.length > 0) { 336 | const start = this.state.text.linePositions[this.state.text.activeLine]; 337 | const thisLine = this.lines[this.activeLine]; 338 | this.lines[this.activeLine] = thisLine.slice(0, thisLine.length - 1); 339 | const relPositions = this.relPositions[this.activeLine]; 340 | this.relPositions[this.activeLine] = this.relPositions[ 341 | this.activeLine 342 | ].slice(0, thisLine.length - 1); 343 | this.setChars(); 344 | this.updatePositions(); 345 | if (thisLine.length === 1) { 346 | this.state.lastPosition = [0, 0]; 347 | } else { 348 | this.state.lastPosition = relPositions[ 349 | relPositions.length - 2 350 | ].slice() as [number, number]; 351 | } 352 | 353 | updateVector(this.state); 354 | this.state.cursor.setEnd( 355 | this.state.lastPosition[0] + this.state.vector.x + start[0], 356 | this.state.lastPosition[1] + this.state.vector.y + start[1] 357 | ); 358 | 359 | this.state.movedCheck = false; 360 | } 361 | } 362 | 363 | enter() { 364 | this.state.setMode("choosePosition"); 365 | return; 366 | } 367 | 368 | setChars() { 369 | let counter = 0; 370 | const offsetBuffer = this.geometry.attributes.offset.array; 371 | // @ts-ignore 372 | offsetBuffer.fill(-1); 373 | for (const line of this.lines) { 374 | for (const char of line.split("")) { 375 | const index = this.chars.indexOf(char); 376 | const col = index % this.perRow; 377 | const row = Math.floor(index / this.perRow); 378 | // @ts-ignore 379 | offsetBuffer[counter * 2] = col; 380 | // @ts-ignore 381 | offsetBuffer[counter * 2 + 1] = this.rows - 1 - row; 382 | counter++; 383 | } 384 | } 385 | this.geometry.attributes.offset.needsUpdate = true; 386 | } 387 | 388 | updatePositions() { 389 | const matrix = new THREE.Matrix4(); 390 | const euler = new Euler(0, 0, 0); 391 | let prev = [0, 0]; 392 | let charCounter = 0; 393 | // clear empty 394 | for (let i = this.lines.length - 1; i >= 0; i--) { 395 | const line = this.lines[i]; 396 | if (this.activeLine !== i && line.length === 0) { 397 | this.lines.splice(i, 1); 398 | this.linePositions.splice(i, 1); 399 | this.relPositions.splice(i, 1); 400 | this.activeLine--; 401 | } 402 | } 403 | for (let j = 0; j < this.lines.length; j++) { 404 | const line = this.lines[j]; 405 | const start = this.linePositions[j]; 406 | this.lineHandles.setPosition(j, start); 407 | for (let k = 0; k < line.length; k++) { 408 | const position = this.relPositions[j][k]; 409 | const x = position[0]; 410 | const y = position[1]; 411 | if (k === 0) prev = [0, 0]; 412 | const rad = Math.atan2(x - prev[0], prev[1] - y); 413 | euler.z = rad - Math.PI / 2; 414 | matrix.makeRotationFromEuler(euler); 415 | matrix.setPosition(x + start[0], y + start[1], 0); 416 | this.setMatrixAt(charCounter, matrix); 417 | if (k === 0) { 418 | prev = [0, 0]; 419 | } else { 420 | prev = [x, y]; 421 | } 422 | charCounter++; 423 | } 424 | } 425 | this.charCounter = charCounter; 426 | this.instanceMatrix.needsUpdate = true; 427 | } 428 | } 429 | 430 | export default Text; 431 | -------------------------------------------------------------------------------- /src/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "custom"; 3 | src: url("data:application/font-woff;base64,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") 4 | format("woff"); 5 | font-weight: 400; 6 | font-style: normal; 7 | } 8 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* ./src/index.css */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | body { 7 | touch-action: none; 8 | } 9 | 10 | canvas { 11 | touch-action: none; 12 | display: block; 13 | } 14 | 15 | html { 16 | } 17 | 18 | ul { 19 | list-style: disc; 20 | padding-left: 24px; 21 | } 22 | a { 23 | text-decoration: underline; 24 | } 25 | 26 | .action-button:hover { 27 | background: rgba(0, 0, 0, 0.125); 28 | } 29 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./font.css"; 4 | import "./index.css"; 5 | import App from "./App"; 6 | import reportWebVitals from "./reportWebVitals"; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById("root") 13 | ); 14 | 15 | // If you want to start measuring performance in your app, pass a function 16 | // to log results (for example: reportWebVitals(console.log)) 17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 18 | reportWebVitals(); 19 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------