[
  {
    "path": ".gitignore",
    "content": "node_modules\nbuild\n\n.DS_STORE\n*-lock.json"
  },
  {
    "path": ".prettierrc",
    "content": "{\n    \"arrowParens\": \"avoid\",\n    \"singleQuote\": true,\n    \"printWidth\": 120,\n    \"tabWidth\": 4,\n    \"trailingComma\": \"none\"\n}"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"editor.codeActionsOnSave\": {\n        \"source.fixAll.eslint\": true\n    },\n    \"editor.formatOnSave\": true,\n    \"eslint.format.enable\": true,\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n    \"[javascript]\": {\n        \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n    }\n}\n"
  },
  {
    "path": "README.md",
    "content": "# ASCII\n\nAn ASCII effect for THREE.js - which runs as a fragment shader on the GPU. Inspired by https://github.com/darkroomengineering/aniso.\n\n### Supported Props\n\n```typescript\ninterface IASCIIEffectProps {\n    characters?: string; // The ASCII characters to use in brightness order dark -> light\n    fontSize?: number; // Font Size of the characters drawn to the texture\n    cellSize?: number; // Size of each cell in the grid\n    color?: string; // Color of the characters\n    invert?: boolean; // Flag which inverts the effect\n}\n```\n\n### Example with @react-three/fiber\n\n```jsx\nimport React from 'react';\nimport { Canvas } from '@react-three/fiber';\nimport { EffectComposer } from '@react-three/postprocessing';\n\nconst Scene = () => {\n    const asciiEffect = React.useMemo(() => new ASCIIEffect(), []);\n\n    return (\n        <Canvas>\n            <mesh>\n                <boxGeometry args={[1, 1, 1]} />\n                <meshPhongMaterial />\n            </mesh>\n            <pointLight position={[0, 0, 10]} />\n            <EffectComposer>\n                <primitive object={asciiEffect} />\n            </EffectComposer>\n        </Canvas>\n    );\n};\n```\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ascii\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"build/index.js\",\n  \"scripts\": {\n    \"build\": \"tsc\"\n  },\n  \"keywords\": [],\n  \"author\": \"Emil Widlund\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@types/three\": \"^0.151.0\",\n    \"postprocessing\": \"6.30.2\",\n    \"three\": \"^0.151.3\",\n    \"typescript\": \"5.0.4\"\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import { CanvasTexture, Color, NearestFilter, RepeatWrapping, Texture, Uniform } from 'three';\nimport { Effect } from 'postprocessing';\n\nconst fragment = `\nuniform sampler2D uCharacters;\nuniform float uCharactersCount;\nuniform float uCellSize;\nuniform bool uInvert;\nuniform vec3 uColor;\n\nconst vec2 SIZE = vec2(16.);\n\nvec3 greyscale(vec3 color, float strength) {\n    float g = dot(color, vec3(0.299, 0.587, 0.114));\n    return mix(color, vec3(g), strength);\n}\n\nvec3 greyscale(vec3 color) {\n    return greyscale(color, 1.0);\n}\n\nvoid mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {\n    vec2 cell = resolution / uCellSize;\n    vec2 grid = 1.0 / cell;\n    vec2 pixelizedUV = grid * (0.5 + floor(uv / grid));\n    vec4 pixelized = texture2D(inputBuffer, pixelizedUV);\n    float greyscaled = greyscale(pixelized.rgb).r;\n\n    if (uInvert) {\n        greyscaled = 1.0 - greyscaled;\n    }\n\n    float characterIndex = floor((uCharactersCount - 1.0) * greyscaled);\n    vec2 characterPosition = vec2(mod(characterIndex, SIZE.x), floor(characterIndex / SIZE.y));\n    vec2 offset = vec2(characterPosition.x, -characterPosition.y) / SIZE;\n    vec2 charUV = mod(uv * (cell / SIZE), 1.0 / SIZE) - vec2(0., 1.0 / SIZE) + offset;\n    vec4 asciiCharacter = texture2D(uCharacters, charUV);\n\n    asciiCharacter.rgb = uColor * asciiCharacter.r;\n    asciiCharacter.a = pixelized.a;\n    outputColor = asciiCharacter;\n}\n`;\n\nexport interface IASCIIEffectProps {\n    characters?: string;\n    fontSize?: number;\n    cellSize?: number;\n    color?: string;\n    invert?: boolean;\n}\n\nexport class ASCIIEffect extends Effect {\n    constructor({\n        characters = ` .:,'-^=*+?!|0#X%WM@`,\n        fontSize = 54,\n        cellSize = 16,\n        color = '#ffffff',\n        invert = false\n    }: IASCIIEffectProps = {}) {\n        const uniforms = new Map<string, Uniform>([\n            ['uCharacters', new Uniform(new Texture())],\n            ['uCellSize', new Uniform(cellSize)],\n            ['uCharactersCount', new Uniform(characters.length)],\n            ['uColor', new Uniform(new Color(color))],\n            ['uInvert', new Uniform(invert)]\n        ]);\n\n        super('ASCIIEffect', fragment, { uniforms });\n\n        const charactersTextureUniform = this.uniforms.get('uCharacters');\n\n        if (charactersTextureUniform) {\n            charactersTextureUniform.value = this.createCharactersTexture(characters, fontSize);\n        }\n    }\n\n    /** Draws the characters on a Canvas and returns a texture */\n    public createCharactersTexture(characters: string, fontSize: number): THREE.Texture {\n        const canvas = document.createElement('canvas');\n\n        const SIZE = 1024;\n        const MAX_PER_ROW = 16;\n        const CELL = SIZE / MAX_PER_ROW;\n\n        canvas.width = canvas.height = SIZE;\n\n        const texture = new CanvasTexture(\n            canvas,\n            undefined,\n            RepeatWrapping,\n            RepeatWrapping,\n            NearestFilter,\n            NearestFilter\n        );\n\n        const context = canvas.getContext('2d');\n\n        if (!context) {\n            throw new Error('Context not available');\n        }\n\n        context.clearRect(0, 0, SIZE, SIZE);\n        context.font = `${fontSize}px arial`;\n        context.textAlign = 'center';\n        context.textBaseline = 'middle';\n        context.fillStyle = '#fff';\n\n        for (let i = 0; i < characters.length; i++) {\n            const char = characters[i];\n            const x = i % MAX_PER_ROW;\n            const y = Math.floor(i / MAX_PER_ROW);\n\n            context.fillText(char, x * CELL + CELL / 2, y * CELL + CELL / 2);\n        }\n\n        texture.needsUpdate = true;\n\n        return texture;\n    }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n      \"lib\": [\"ESNext\", \"DOM\"],\n      \"target\": \"ESNext\",\n      \"module\": \"ESNext\",\n      \"strict\": true,\n      \"moduleResolution\": \"node\",\n      \"esModuleInterop\": true,\n      \"skipLibCheck\": true,\n      \"downlevelIteration\": true,\n      \"outDir\": \"build\",\n      \"declaration\": true,\n      \"jsx\": \"react\"\n    },\n    \"include\": [\"./src\"]\n  }\n  "
  }
]