{"id":13432318,"url":"https://github.com/ninjadev/nin","last_synced_at":"2025-04-05T14:09:55.300Z","repository":{"id":53530995,"uuid":"13017625","full_name":"ninjadev/nin","owner":"ninjadev","description":"nin is ninjatool","archived":false,"fork":false,"pushed_at":"2022-10-23T09:47:32.000Z","size":38974,"stargazers_count":342,"open_issues_count":10,"forks_count":15,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-03-29T13:09:29.469Z","etag":null,"topics":["demo","demoscene","demotool","javascript","nin","png-compression","shaders","tooling","webpack","yarn"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/ninjadev-nin","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ninjadev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-09-22T17:30:37.000Z","updated_at":"2025-02-02T20:39:01.000Z","dependencies_parsed_at":"2022-09-18T21:38:42.693Z","dependency_job_id":null,"html_url":"https://github.com/ninjadev/nin","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninjadev%2Fnin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninjadev%2Fnin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninjadev%2Fnin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninjadev%2Fnin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ninjadev","download_url":"https://codeload.github.com/ninjadev/nin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247345856,"owners_count":20924102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["demo","demoscene","demotool","javascript","nin","png-compression","shaders","tooling","webpack","yarn"],"created_at":"2024-07-31T02:01:10.300Z","updated_at":"2025-04-05T14:09:55.277Z","avatar_url":"https://github.com/ninjadev.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Demomaking"],"sub_categories":["Tools"],"readme":"\u003ch1 align=center\u003en i n\u003c/h1\u003e\n\n\u003cp align=center\u003e \u003ca href=\"#user-manual\"\u003eUser manual\u003c/a\u003e • \u003ca href=\"#developer-manual\"\u003eDeveloper manual\u003c/a\u003e • \u003ca href=\"#testimonials--reviews\"\u003eTestimonials / Reviews\u003c/a\u003e • \u003ca href=\"#list-of-known-nin-projects\"\u003eList of known nin projects\u003c/a\u003e\u003c/p\u003e\n\n\n\u003e \u003csup\u003e*DISCLAIMER*: This is a tool created for internal use by Ninjadev, and is open sourced to share ideas with and get feedback from the community.\nYou are free to use it, according to the License, but we will not necessarily provide support and may at any time add, change or remove features as we require for our productions.\u003c/sup\u003e\n\n![](https://github.com/ninjadev/nin/raw/master/nin-preview.PNG)\n\n\n\u003e nin is ninjatool\n\nnin is Ninjadev's internal demo tool.\nIt is a tool for easing development of browser-based WebGL demos.\nCore features include:\n\n- Node-based demotool, effortlessly reuse your effects, scenes, and create crazy transitions.\n- Livereloading of shaders and scenes in the browser. No more manual recompilation!\n- Tight THREE.js integration for all your WebGL needs.\n- Compile and pack your WebGL demo to a .png.html file for easy compo delivery\n\nThe backend component is written in node.js, and keeps track of and recompiles changed files.\nThe frontend is created using React, and communicates with the backend over websockets.\n\n# User manual\n\n## Installing nin\n\nTo install nin simply run the command:\n```bash\nnpm install -g ninjadev-nin\n```\nThis projects requires node version `7.9.0` or newer.\nYou can install node from packaging [here](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) or download zipped source from [this page](https://nodejs.org/en/download/current/) and verify signatures with the guide [here](https://github.com/nodejs/node/#verifying-binaries);\n\nYou will also need `git` installed for project generation to work.\nNew project created with nin get a default .eslintrc suitable for WebGL demos with THREE.js.\n\n## Getting started\n\nRunning `nin new \u003cdirname\u003e` will create the specified directory and initialize a new nin project inside.\nRunning `nin run` inside the newly created project will make it accessible on http://localhost:8000.\n\nnin creates a manifest file called `nin.json`.\nYou should fill out this file with the title of your demo, the authors, a description, song metadata, and even a google analytics tracking code to collect statistics.\nThe metadata is then used to generate html meta-tags in the head of the demo,\nas well as in the metadata segment of the `.png.html` file.\nThe png metadata can be viewed with a command such as `pngcheck -c -t -7 bin/demo.png.html` on linux.\n\n## My First Node\n\nCreate a new node by clicking `Generate -\u003e THREE NODE` in the frontend menu.\nThe node will be placed in `src/nodeName.js` and added to the graph in `res/graph.json`.\nYou must connect the node yourself to the output node.\nThis is done by setting `connected.screen` to `nodeName.render` as in the example below,\nwhere `nodeName` is the id of the node you want to connect to the display.\n\n```json\n{\n    \"id\": \"root\",\n        \"type\": \"NIN.RootNode\",\n        \"connected\": {\n            \"screen\": \"nodeName.render\"\n        }\n    }\n}\n```\n\n## Shader Nodes\n\nCreate a shader node by clicking `Generate -\u003e Shader Node` in the frontend menu.\nIt generates the file `src/nameOfTheShaderNode.js` and the folder `src/shaders/nameOfTheShaderNode/`.\nTo get livereload on shader change, you shader must be specified in the options object of your node in `res/graph.json`,\nthe shader generator will do this for you.\nIf needed, you can access the shader through the global `SHADERS` object, by writing `SHADERS.nameOfTheShader`.\n\n## Compiling\n\nThe `nin compile` command will create a single file `bin/demo.png.html` that contains all the code and resources of your demo.\nBase64 and PNG compression magic is used to achieve this.\nIt will at the same time output a file without PNG compression, `bin/demo.html`, which will be slightly larger, but compatible with a wider range of devices (especially smartphones).\nFor faster compiles, pass the flag `--no-closure-compiler`. This will only concatenate js files, without any minifying.\n\n## .ninrc\n\nSome of nin's settings can be overriden by placing a .ninrc file in your home directory.\nCurrently, keyboard shortcuts is the only behavior which can be changed in the .ninrc.\nThe list of canonical names for keybindings can be found in\n[nin/frontend/app/scripts/directives/menubar.js](https://github.com/ninjadev/nin/blob/master/nin/frontend/app/scripts/directives/menubar.js).\n\nAn example .ninrc looks like the following:\n\n```\n[keybinds]\nstartRendering=left\nstopRendering=right\n```\n\n## Rendering to video\n\n1. `nin run`.\n1. Open nin in your browser, navigate to the frame you want to render from, and press R. This will start dumping single frames as numbered .PNGs in `bin/render/`  in your project folder.\n1. Refresh etc every time WebGL crashes.\n1. When you have rendered all the frames: `nin render`. You need `ffmpeg` ([ffmpeg.org](http://ffmpeg.org/)) installed for this.\n1. The demo is now available at `bin/render/render.mp4`.\n\nEach frame will take up to around 4MB on disk, and the finished .mp4 will be on the order of 1GB when rendered, so make sure you have enough disk space.\nExpect to render maybe a frame or two per second.\n\n# Developer manual\n\n## Setup\n\nYou will need to have node installed.\n\nRunning `make` in the nin folder will build and compile the entire project.\nRunning `npm link` will add nin to your node binaries path, making it available globally.\n\n## Developing\n\nFirst, run `nin run` inside your project.\nIf you wish to develop on the frontend, running `make run` inside `nin/frontend/` makes webpack rebuild the frontend on file change.\nYou only need to rerun `nin run` if you change files in either `nin/dasBoot` or `nin/backend`.\n\n### Working with scenes\n\nUsually, your demo will be a series of connected scenes. This section covers how to work with them.\n\n#### Adding scenes\n\nAdding a scene is quite straight forward, once you become good friends with graph.json!\n\n##### How to add the first scene to a demo\n\nThis is what you need to to do get your first scene wired up and ready to go!\n\n##### How to add a scene at the end of the demo\n\nHere is an example of how to prolong the demo by adding a new scene at the end.\n\n##### How to add a new scene between two existing scenes\n\nSometimes you want to squeeze in a new scene between two other scenes that you already have in your demo.\nOften you will perhaps only shorten the preceding scene and leave the start of the following scene intact.\nBut for completeness, here is an example where we both shorten the previous scene, and chop down the beginning of the following scene.\n\n#### Adding backgrounds to scenes\n\nAdding backgrounds ain't all that hard, once you have an overview of whats needed!\n\nFirst of all, make sure there is an actual existing file containing the code for your background.\nIf you want to render your background on a canvas the content should be something like the example below.\nNote that to actuall work with the content, you will probably want to be able to see it first.\nTo make the content visible, you should go through the steps with adding it the scene and in graph.json, as described below the example.\n\n```js\n(function(global) {\n  const F = (frame, from, delta) =\u003e (\n    frame - FRAME_FOR_BEAN(from)) / (FRAME_FOR_BEAN(from + delta) - FRAME_FOR_BEAN(from)\n  );\n\n  constructor(id) {\n    super(id, {\n      outputs: {\n        render: new NIN.TextureOutput()\n      }\n    });\n\n    this.canvas = document.createElement('canvas');\n    this.ctx = this.canvas.getContext('2d');\n\n    this.width = 1024;\n    this.height = 1024;\n\n    this.canvas.width = this.width;\n    this.canvas.height = this.height;\n\n    this.output = new THREE.VideoTexture(this.canvas);\n    this.output.minFilter = THREE.LinearFilter;\n    this.output.magFilter = THREE.LinearFilter;\n\n    // ... other constructor content, such as initializing your Random.\n  }\n\n  update(frame) {\n    super.update(frame);\n    // Other stuff you want to do each frame\n  }\n\n  // The warmup block exsist to prevent stutter related to compiling shaders when the scene starts.\n  // Usually shaders are compield for your GPU just in time at the moment they are first called.\n  // This can take some time, and result in perceived jitter at the start of your scene.\n  // To prevent this, when NIN starts running a demo, before the playback looks like it begins, it will run through each scene and call its `warmup`.\n  // In practice, this is like rendering one frame from each scene, so that all shaders are loaded before the smooth playback you want to show off.\n  warmup(renderer) {\n    this.update(6710); // the first frame in this scene cointaining the shaders you want pre-loaded.\n    this.render(renderer);\n  }\n\n  render() {\n    this.output.needsUpdate = true;\n    this.outputs.render.setValue(this.output);\n  }\n\n  global.greetsBackgroundCanvas = greetsBackgroundCanvas;\n})(this);\n```\n\nOnce you've decided on a name for the background-class, you should add it to the graph.json (`.../demo_repository/res/graph.json`).\nIt has to be added both as a separate node, and as an input to the scene you wish to use it in.\nExample:\n\n```jsonc\n[\n  // loads of graph.json content\n  // ...\n  // },\n  {\n    \"id\": \"the_scene\",\n    \"type\": \"the_scene\",\n    \"connected\": {\n      \"param_name\": \"background_class_name.render\"\n    }\n  },\n  // more graph.js\n  // ...\n  // },\n  {\n    \"id\": \"background_class_name\",\n    \"type\": \"background_class_name\"\n  }\n  // possibly more graph.json, but you've just added this, right? =P\n]\n```\n\nNote the `.render` after the backgrounds class name in the `connected` section!\n\nFinally, you need to add the new background to your scene.\nThis recquires a couple of things.\n\nFirst, you need to take make sure the background class is declared as an input in your constructors call to super.\nThen, in your constructor, you need to define the mesh your background class will draw on, its size and location, and add it to your scene.\n\nAt this point you should be mindfull of whether you make your background render from both sides, or only the front or back side.\nIf you make a background in the form of a \"sky box\" that engulfs the scene in front of the camera, you'll likely want to set the drawing side to `side: THREE.BackSide`.\n(This is handy if you have a lot of camera movement and can't guarantee that that anything in your scene wil neccessarily be infront of any given plane that you might have wanted to use as a background.)\nDoing so should prevent your background from rendering in front of the scene you are trying to display and blocking the view of it.\n\nAfter that, in your scenes `update`-function, you need to ensure that your background also gets updated.\nThat is done by setting assigning materials' (mesh you declared in the constructors') map to the background-input.\nNote that you at this point probably should also set the flag for that it needs updating.\n\nLastly, you should be aware that in regards to the camera and general movement in the scene, the backgound can be considered just like any other object.\nThis implies that for a scene with a lot of camera movement, you can both make the background an object that moves together with the camera and appears static, or you can make it rotate wildly.\n\nExample:\n\n```js\n(function(global) {\n  class the_scene extends NIN.THREENode {\n    constructor(id, options) {\n      super(id, {\n        camera: options.camera,\n        outputs: {\n          render: new NIN.TextureOutput()\n        },\n        inputs: {\n          background: new NIN.TextureInput()\n        }\n        // Other inputs to super\n      });\n    }\n    // more constructor input\n    this.background = new THREE.Mesh(\n      new THREE.BoxGeometry(skybox_x_size_in_scene, skybox_y_size_in_scene, skybox_z_size_in_scene), // Note that these are independent of the dimentions you declare for the canvas in the background. E.g. you can work on a 1024 x 1024 x 1 canvas in your background that is spread out on a 3440 x 1440 x 2048 surface in the scene. You'd probably want to make the aspect ratios equivalent or draw one flat surface on each side of a 3d box in your scene though)\n      new THREE.MeshBasicMaterial({color: 0xffffff, side: THREE.BackSide})\n    );\n    this.scene.add(this.background);\n    // other constructor code\n  }\n\n  update(frame) {\n      super.update(frame);\n      this.background.material.map = this.inputs.background.getValue();\n      this.background.material.needsUpdate = true;\n      // ...\n      // The actual focus of your scene!\n      // ...\n      // If this is the camera movement in your scene\n      var camera_angle = frame * related + math / expression;\n      var this.camera.position.x = initial_position_x + Math.sin(camera_angle) * speed_multiplier;\n      var this.camera.position.y = initial_position_y + Math.cos(camera_angle) * speed_multiplier;\n      var this.camera.position.z = initial_position_z;\n      this.camera.lookAt(new THREE.Vector3(initial_position_x, initial_position_y, poi_z));\n      // Note that the initial_position_... above ^ can be linear expressions related to the frame\n      // Then you can do like this to to keep the background still relative to the camera:\n      this.background.rotation.z = -cameraAngle;\n\n      // Or move it relative to the camere at a pace independently of the cameras movement relative to the point it is directed at:\n      this.background.rotation.z = -cameraAngle + Math.sin(frame_related * constants + and_or_bean_related_movement_params / Math.PI * related_becuase_radians) * factor_controllong_intensity + other_linear_offsets;\n\n      // You can also apply transforms to the background relative to the camera here:\n      this.background.scale.x = -1; // Inverts / mirrors the background along the x-axis\n  }\n})(this);\n```\n\n### Randomness\n\nTo ensure that random things happen consistently across runs it is recommended to use the `Random`-class rather than `Math.random()`.\nSet up `Random` by initializing it in your constructor like so:\n```js\nconstructor(id, options) {\n  ...\n  this.random = new Random('seedString');\n  ...\n}\n```\n\nThen use the random generator you just created like this:\n```js\nvar randomNumber = this.random();\n```\n\n### Time and timed events\n\nA lot of making a demo is syncing what's happening on the screen with the music.\n\nA `BEAN` can be described as the smallest possible resoluton of the beat.\n\nWorking with `BEAN`s directly will usually not give you a smooth 60 FPS animation.\n`BEAN`s are incremented less often than 60 FPS. A common approach is using `frame` with `FRAME_FOR_BEAN` instead.\nE.g. Instead of doing\n```js\nvar startBean = 1;\nvar endBean = 20;\nvar fractionIn = (BEAN - startBean) / (endBean - startBean);\n```\ndo\n```js\nvar startFrame = FRAME_FOR_BEAN(1);\nvar endFrame = FRAME_FOR_BEAN(20);\nvar fractionIn = (frame - startFrame) / (endFrame - startFrame);\n```\n\n|Counter|Description|Typical way to access|\n| ----- |-----------| ------------------- |\n| Bean | The smallest possible resoluton of the beat. | `BEAN` |\n| Frame | Monotonously counts upwards. Usually what you want to use in your `update()`-function. | `frame` (if you need frame inside your `render` method, you can store it on `this` inside the `update` method) |\n| Beat | ToDo | ToDo |\n| Bar | ToDo | ToDo |\n\n#### Notes on the `update(frame)`-function\n\nIn your scene-function, you can define an update function.\n\n```js\nupdate(frame) {\n  // The coolest of code\n}\n```\n\nThis will run for every frame.\n\n### Movement\n\nYou might have defined an object in your scene, such as a box, the camera, or a light source, which you want to move within your scene. To aid you in doing this smoothly, there are some predefined functions you can utilize.\n\nThe most important ones are:\n\n* smoothstep\n* lerp\n\nThey all have the same API:\n`lerp(startValue, endValue, t)`\n\nWhen `t` is smaller than or equal to 0, `startValue` is returned.\nWhen `t` is between 0 and 1, a value between startValue and endValue is returned, depending on which interpolation function you're using.\nWhen `t` is larger than 1, `endValue` is returned.\n\n#### smoothstep\n\nFor more details, check out http://en.wikipedia.org/wiki/Smoothstep .\n\n#### lerp\n\n### Music\n\n#### How to add music to your demo\n\nIn the `nin.json`-file you can define the `music`-section directly in the root.\nHere you can specify\n\n* `path`: relative path to the music file\n* `bpm`: the tempo of your music in beats per minute\n\nSample music configuration:\n\n```json\n\"music\": {\n  \"path\": \"res/music.mp3\",\n  \"bpm\": 190,\n  \"subdivision\": 12,\n  \"BEANOffset\": 0\n},\n```\n\nFor details on how this section is processed further you can check out `nin/dasBoot/BEATBEAN.js`.\n\n### Text\n\nHow to write text, or letters in general, to your 2D canvas.\n\nOut of the box you can write text to your canvas like this:\n\n```js\nthis.canvas.getContext('2d').fillStyle = '#ffffff'; // The color of the text you are going to be displaying\nthis.canvas.getContext('2d').font = 'bold ' + (24) + 'pt Arial'; // Pattern: weight [size]size_unit font_name\nthis.canvas.getContext('2d').textAlign = 'center'; // possible values: left, right, center, start (locale dependent), end (locale dependent)\nthis.canvas.getContext('2d').textBaseline = 'middle'; // possible values: middle, top, bottom, alphabetic, hanging, ideographic\nthis.canvas.getContext('2d').fillText('Hello Nin!', x_position_on_canvas, y_position_on_canvas);\n```\n\nBeware that this will not replace any text previously drawn. To obtain a blank slate you have to either clear or draw over previously shown text.\n\nReference for styling of text drawn with `fillText`: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text#Styling_text .\n\n## Linting\n\nThe frontend part of this project uses ESLint for linting.\nSee the `.eslintrc.js` file in the frontend part of this project.\n\nThe demo itself and our own `dasBoot` uses the Google Closure Linter, please see this link for installation information.\n`https://developers.google.com/closure/utilities/docs/linter_howto`\nUse the `--nojsdoc` flag.\n\n## Publishing nin\n\nTo publish nin, checkout a new branch, run `make bump-version`, `git push`, get\nit merged, and Travis will automatically publish a new release after master has\nfinished building.\n\n## Notes on working in windows\n\nThe prerequisites remain the same, you at least need Node.\n\nTo build and compile the entire project, for now, you need only run `npm start` in the root of the nin-repo.\nTo run nin without linking up through npm you can replace the `nin`-command with `node path-to-ninrepo/nin/backend/nin`.\nE.g. when you are in a project folder of a demo, and you want to run it with your freshly compiled nin directly, you can run `node path-to-ninrepo/nin/backend/nin run` instead of `nin run`.\n\nIf you want to run it from powershell regularly, you might want to make an alias in your profile akin to this:\n\n```powershell\nfunction nin\n{\n    param($argz)\n    node $ninRepoPath\\nin\\backend\\nin $argz\n}\n```\n\nAlternatively you can use the `psNin.ps1` script from your demo, or call it from anywhere if you supply it to the optional `$demoPath` parameter.\n\n# Testimonials / Reviews\n\n\u003e nice! - [mrdoob](https://twitter.com/mrdoob/status/686575651923574790)\n\n\u003c!-- --\u003e\n\n\u003e Oh man, I didn't know you guys released your tools. I'm a big fan of your stuff -- awesome to see such polished prods on the web. Happy to have helped enable some amazing work! - [daeken](https://news.ycombinator.com/item?id=12264461#unv_12265590)\n\n# List of known nin projects\n\n- [Ninjacon 2016: Sea Shark Invtro](https://github.com/stianjensen/ninjacon-invite)\n- [Everything is Fashion](https://github.com/ninjadev/tyve)\n- [Hold Kjæft](https://github.com/Raane/HoldKjeft)\n- [Heatseeker](https://github.com/sigvef/heatseeker)\n- [Inakuwa Oasis](https://github.com/ninjadev/en)\n- [Stars and boxes](https://github.com/iver56/abel-demo-14)\n- [Crankwork Steamfist](https://github.com/ninjadev/dix)\n- [What Are You Syncing About?](https://github.com/ninjadev/re)\n- [Zeven](https://github.com/ninjadev/zeven)\n- [No Invitation](https://github.com/ninjadev/revision-invite-2018)\n- [Pinky Frinky](https://github.com/ninjadev/pluss)\n- [Look Closer](https://github.com/ninjadev/zoo)\n- [Neon Fantasy](https://github.com/iver56/neon-fantasy)\n- [Flat Shade Society](https://github.com/ninjadev/si)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fninjadev%2Fnin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fninjadev%2Fnin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fninjadev%2Fnin/lists"}