{"id":22300700,"url":"https://github.com/paladin-t/jjj","last_synced_at":"2026-04-17T15:32:05.103Z","repository":{"id":219698523,"uuid":"748941042","full_name":"paladin-t/JJJ","owner":"paladin-t","description":"A 3D toolkit and framework based on Three.js.","archived":false,"fork":false,"pushed_at":"2024-01-29T10:05:59.000Z","size":2121,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-22T04:32:11.910Z","etag":null,"topics":["3d","engine","framework","game","jjj","model","nodejs","npm","threejs","toolkit","viewer"],"latest_commit_sha":null,"homepage":"https://paladin-t.github.io/tiny/jjj/index.html","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/paladin-t.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-27T05:16:47.000Z","updated_at":"2024-01-29T08:31:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"12f7f9d0-fd4c-47a2-aafb-7c78bf6272e3","html_url":"https://github.com/paladin-t/JJJ","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"b645af9a5b88a30d95ad3cbde84fe894e8a2ce4c"},"previous_names":["paladin-t/jjj"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paladin-t%2FJJJ","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paladin-t%2FJJJ/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paladin-t%2FJJJ/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paladin-t%2FJJJ/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paladin-t","download_url":"https://codeload.github.com/paladin-t/JJJ/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245559447,"owners_count":20635357,"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":["3d","engine","framework","game","jjj","model","nodejs","npm","threejs","toolkit","viewer"],"created_at":"2024-12-03T18:13:35.110Z","updated_at":"2026-04-17T15:32:05.060Z","avatar_url":"https://github.com/paladin-t.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"imgs/logo.png\" width=\"128\"\u003e\n\n\u003chr\u003e\n\n[JJJ (3Js)](https://paladin-t.github.io/tiny/jjj/index.html) is a 3D toolkit and framework based on Three.js. It implements a data-driven DSL to setup, manipulate and control resources, objects, materials and behaviours in the game world. It also offers a number of toolkits to help make your final game, for the moment it offers the following tools:\n\n* [3D model viewer](https://paladin-t.github.io/tiny/jjj/viewer.html)\n\nMore features, tools and examples are WIP.\n\n\u003chr\u003e\n\n## Table of Content\n\n- [Table of Content](#table-of-content)\n- [Installing](#installing)\n- [API](#api)\n  - [Creating Game World](#creating-game-world)\n  - [Setup and Update](#setup-and-update)\n  - [Callbacks](#callbacks)\n  - [Constructing Scene Graph](#constructing-scene-graph)\n    - [Applying Controller to Objects](#applying-controller-to-objects)\n    - [Loading Model to Scene Graph](#loading-model-to-scene-graph)\n    - [Animating Model](#animating-model)\n  - [Posting Message](#posting-message)\n  - [Querying Objects](#querying-objects)\n- [License](#license)\n\n\u003chr\u003e\n\n## Installing\n\nJJJ is Configured with Webpack as a bundler.\n\nClone the project and install dependencies:\n\n```bash\nnpm i jjj.js\n```\n\nor:\n\n```bash\ngit clone https://github.com/paladin-t/JJJ.git\nnpm i\n```\n\nStart a webpack development server:\n\n```bash\nnpm run start\n```\n\nMake a build:\n\n```bash\nnpm run build\n```\n\n\u003chr\u003e\n\n## API\n\n### Creating Game World\n\n```js\nconst game = new Game({\n  mode: 'default',\n  canvas\n});\n```\n\n* Creates a game object\n  * `mode: string | class constructor | object`: the game mode, can be one of the following \"Game Modes\" when it is string typed\n  * `canvas: Canvas | null`: the canvas object to create the game on, or `null` to let the framework to create one\n\n| Game Modes | Note |\n|---|---|\n| 'default' | The type of a generic game |\n\n```js\nconst world = game.get('#world');\n```\n\n* Gets the world object of a game\n\n### Setup and Update\n\nMost of the operations are described in a JSON-based DSL.\n\n```js\nawait world\n  .execute(\n    {\n      \"commands\": [\n        {\n          \"command\": \"setup\",\n          \"renderer\": [\n            {\n              \"type\": \"default\",\n              \"outputColorSpace\": THREE.SRGBColorSpace,\n              \"width\": window.innerWidth, \"height\": window.innerHeight,\n              \"pixelRatio\": window.devicePixelRatio,\n              \"enableShadow\": true,\n              \"shadowType\": THREE.PCFShadowMap,\n              \"options\": null\n            }\n          ],\n          \"camera\": {\n            \"type\": \"perspective\",\n            \"aspect\": 60,\n            \"near\": 0.1, \"far\": 100,\n            \"position\": [ 0, 1.8, 3 ],\n            \"target\": [ 0, 1, 0 ]\n          }\n        }\n      ]\n    }\n  );\n```\n\n* Setup renderer and camera for the specific game world\n  * `renderer[n].type` can be one of the following \"Renderer Types\"\n  * `camera.type` can be either \"perspective\" or \"orthographic\", see the following examples\n\n| Renderer Types | Note |\n|---|---|\n| 'default' | The type of a standard renderer |\n| 'bloom' | The type of a bloom renderer |\n| 'toon' | The type of a toon renderer |\n\n\u003cdetails\u003e\n\u003csummary\u003ePerspective/Orthographic Camera\u003c/summary\u003e\n\n```js\n...\n\"camera\": {\n  \"type\": \"perspective\",\n  \"aspect\": 60,\n  \"near\": 0.1, \"far\": 100,\n  \"position\": [ 0, 1.8, 3 ],\n  \"target\": [ 0, 1, 0 ]\n}\n...\n```\n\n```js\n...\n\"camera\": {\n  \"type\": \"orthographic\",\n  \"left\": -2, \"right\": 2,\n  \"top\": 2, \"bottom\": -2,\n  \"near\": 0.1, \"far\": 100,\n  \"position\": [ 0, 1.8, 3 ],\n  \"target\": [ 0, 1, 0 ]\n}\n...\n```\n\n\u003c/details\u003e\n\n```js\nfunction render() {\n  game.update();\n\n  requestAnimationFrame(render);\n}\nrender();\n```\n\n### Callbacks\n\nMost of the API will feedback to specific callback entries.\n\n\u003cdetails\u003e\n\u003csummary\u003eCallbacks\u003c/summary\u003e\n\n```js\nconst callbacks = {\n  onWorldSetup: (what) =\u003e {\n    console.log('Setup world', what);\n  },\n\n  onNodePending: (what) =\u003e {\n    console.log('Node pending', what);\n  },\n  onNodeLoaded: (what) =\u003e {\n    console.log('Loaded node', what);\n  },\n  onNodeUnloaded: (what) =\u003e {\n    console.log('Unloaded node', what);\n  },\n  onNodeAdded: (what) =\u003e {\n    console.log('Added node', what);\n  },\n  onNodeAnimationAdded: (what) =\u003e {\n    console.log('Node animation added', what);\n  },\n  onNodeError: (what) =\u003e {\n    console.log('Node error', what);\n  },\n  onMaterialLoaded: (what) =\u003e {\n    console.log('Loaded material', what);\n  },\n\n  onControllerApplied: (what) =\u003e {\n    console.log('Controller applied', what);\n  },\n  onControllerRemoved: (what) =\u003e {\n    console.log('Controller removed', what);\n  },\n\n  onActed: (data) =\u003e {\n    console.log('Acted', data);\n  },\n  onAnimationStarted: (data) =\u003e {\n    console.log('Animation started', data);\n  },\n  onAnimationFinished: (data) =\u003e {\n    console.log('Animation finished', data);\n  },\n\n  onReturned: (data) =\u003e {\n    console.log('Got actions', data);\n  }\n};\n```\n\n\u003c/details\u003e\n\n### Constructing Scene Graph\n\n```js\nawait world\n  .execute(\n    {\n      \"commands\": [\n        {\n          \"command\": \"load\",\n          \"where\": \"#scene\",\n          \"await\": true,\n          \"nodes\": [\n            {\n              \"type\": \"hemi_light\",\n              \"skyColor\": 0xcccccc, \"groundColor\": 0x444444, \"intensity\": 2.0,\n              \"position\": [ 0, 0, 0 ]\n            },\n            {\n              \"type\": \"ambient_light\",\n              \"color\": 0xffffff, \"intensity\": 1.0\n            },\n            {\n              \"type\": \"directional_light\",\n              \"color\": 0xffffff, \"intensity\": 1.5,\n              \"position\": [ 1, 2.5, 1 ],\n              \"target\": [ 0, 0, 0 ],\n              \"castShadow\": true,\n              \"shadow\": {\n                \"top\": 2, \"bottom\": -2,\n                \"left\": -2, \"right\": 2,\n                \"near\": 0.01, \"far\": 10,\n                \"bias\": 0.001,\n                \"mapWidth\": 2048, \"mapHeight\": 2048\n              }\n            },\n            {\n              \"type\": \"object3d\",\n              \"name\": \"AvatarRoot\",\n              \"position\": [ 0, 0, 0 ],\n              \"scale\": [ 1, 1, 1 ],\n              \"rotation\": [ 0, 0, 0 ]\n            },\n            {\n              \"type\": \"geometry\",\n              \"name\": \"Ground\",\n              \"geometry\": \"plane\",\n              \"width\": 100, \"height\": 100, \"widthSegments\": 1, \"heightSegments\": 1,\n              \"position\": [ 0, -0.01, 0 ],\n              \"rotation\": [ -Math.PI / 2, 0, 0 ],\n              \"receiveShadow\": true,\n              \"material\": {\n                \"type\": \"shadow\",\n                \"opacity\": 0.5\n              }\n            },\n            {\n              \"type\": \"geometry\",\n              \"name\": \"Background1\",\n              \"geometry\": \"plane\",\n              \"width\": 5, \"height\": 5, \"widthSegments\": 1, \"heightSegments\": 1,\n              \"position\": [ 0, 2.48, -2 ],\n              \"rotation\": [ 0, 0, 0 ],\n              \"receiveShadow\": false,\n              \"material\": {\n                \"type\": \"basic\",\n                \"texture\": {\n                  \"src\": \"images/wall.png\",\n                  \"minFilter\": THREE.LinearFilter,\n                  \"magFilter\": THREE.LinearFilter\n                },\n                \"transparent\": true\n              },\n              \"children\": [\n                {\n                  \"type\": \"geometry\",\n                  \"name\": \"Background2\",\n                  \"geometry\": \"plane\",\n                  \"visible\": true,\n                  \"width\": 5, \"height\": 5, \"widthSegments\": 1, \"heightSegments\": 1,\n                  \"position\": [ 0, -2.5, 2 ],\n                  \"rotation\": [ -Math.PI * 0.5, 0, 0 ],\n                  \"receiveShadow\": false,\n                  \"material\": {\n                    \"type\": \"basic\",\n                    \"texture\": {\n                      \"src\": \"images/ground.png\",\n                      \"minFilter\": THREE.LinearFilter,\n                      \"magFilter\": THREE.LinearFilter\n                    },\n                    \"transparent\": true\n                  }\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    },\n    callbacks\n  );\n```\n\n#### Applying Controller to Objects\n\n```js\nawait world\n  .execute(\n    {\n      \"commands\": [\n        {\n          \"command\": \"control\",\n          \"controllers\": [\n            {\n              \"where\": \"#camera\",\n              \"name\": \"CameraZoomController\",\n              \"type\": \"orbit\",\n              \"target\": [ 0, 1.5, 0.001 ],\n              \"enableDamping\": false,\n              \"enableZoom\": true,\n              \"minDistance\": 0.5, \"maxDistance\": 5,\n              \"enableRotate\": false,\n              \"enablePan\": false\n            },\n            {\n              \"where\": \"#scene.AvatarRoot\",\n              \"name\": \"AvatarRotationController\",\n              \"type\": \"orbit\",\n              \"target\": [ 0, 0, 0.001 ],\n              \"enableDamping\": true, \"dampingFactor\": 0.05,\n              \"enableZoom\": false,\n              \"rotateSpeed\": -1,\n              \"maxPolarAngle\": Math.PI / 2, \"minPolarAngle\": Math.PI / 2\n            }\n          ]\n        }\n      ]\n    },\n    callbacks\n  );\n```\n\n#### Loading Model to Scene Graph\n\n```js\nawait world\n  .execute(\n    {\n      \"commands\": [\n        {\n          \"command\": \"load\",\n          \"await\": true,\n          \"where\": \"#scene.AvatarRoot\",\n          \"nodes\": [\n            {\n              \"type\": \"model\",\n              \"name\": \"Avatar\",\n              \"src\": \"example.glb\",\n              \"position\": [ 0, 0, 0 ],\n              \"scale\": [ 1, 1, 1 ],\n              \"rotation\": [ 0, 0, 0 ],\n              \"castShadow\": true,\n              \"material\": {\n                \"depthWrite\": true,\n                \"metalness\": 0.1\n              }\n            }\n          ]\n        }\n      ]\n    },\n    callbacks\n  );\n```\n\n#### Animating Model\n\n```js\nawait world\n  .execute(\n    {\n      \"commands\": [\n        {\n          \"command\": \"control\",\n          \"controllers\": [\n            {\n              \"where\": \"#scene.AvatarRoot.Avatar\",\n              \"name\": \"AvatarAnimationController\",\n              \"type\": \"animation\",\n              \"default\": {\n                \"clip\": \"idle\",\n                \"loop\": true,\n                \"weight\": 1\n              },\n              \"clips\": [\n                {\n                  \"clip\": \"*\",\n                  \"loop\": true,\n                  \"weight\": 1\n                },\n                {\n                  \"clip\": \"idle\",\n                  \"loop\": true,\n                  \"weight\": 1\n                },\n                {\n                  \"clip\": \"run\",\n                  \"loop\": true,\n                  \"weight\": 1\n                },\n                {\n                  \"clip\": \"walk\",\n                  \"loop\": true,\n                  \"weight\": 1\n                },\n                {\n                  \"clip\": \"talk\",\n                  \"loop\": true,\n                  \"weight\": 1\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    },\n    callbacks\n  );\n```\n\n### Posting Message\n\n```js\nworld\n  .postMessage(\n    \"#scene.AvatarRoot.Avatar.controllers.animation\",\n    {\n      \"message\": \"GET_ACTIONS\"\n    },\n    {\n      onReturned: (data) =\u003e {\n        console.log('Got actions', data);\n      }\n    }\n  );\n```\n\n### Querying Objects\n\n```js\nconst scene = world.query('#scene');\n\nconst avatar = world.query('#scene.AvatarRoot.Avatar');\n\nconst foo = world.query([\n  '#scene.some.node',\n  {\n    byUUID: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'\n  },\n  {\n    byType: 'Object3D'\n  },\n  {\n    byIndex: 42\n  },\n  'material'\n]);\n```\n\n\u003chr\u003e\n\n## License\n\nJJJ is distributed under the MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaladin-t%2Fjjj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaladin-t%2Fjjj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaladin-t%2Fjjj/lists"}