{"id":15282469,"url":"https://github.com/danielesteban/dudes","last_synced_at":"2025-04-12T23:03:52.086Z","repository":{"id":57216986,"uuid":"371420425","full_name":"danielesteban/dudes","owner":"danielesteban","description":"Yet another voxels engine","archived":false,"fork":false,"pushed_at":"2021-10-24T08:54:16.000Z","size":7881,"stargazers_count":30,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-26T17:07:07.744Z","etag":null,"topics":["threejs","voxels","wasm","webxr"],"latest_commit_sha":null,"homepage":"https://dudes.gatunes.com","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/danielesteban.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"danielesteban"}},"created_at":"2021-05-27T15:26:01.000Z","updated_at":"2024-11-11T17:14:00.000Z","dependencies_parsed_at":"2022-08-28T21:01:12.058Z","dependency_job_id":null,"html_url":"https://github.com/danielesteban/dudes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielesteban%2Fdudes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielesteban%2Fdudes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielesteban%2Fdudes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielesteban%2Fdudes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielesteban","download_url":"https://codeload.github.com/danielesteban/dudes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248262433,"owners_count":21074308,"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":["threejs","voxels","wasm","webxr"],"created_at":"2024-09-30T14:26:19.934Z","updated_at":"2025-04-12T23:03:52.054Z","avatar_url":"https://github.com/danielesteban.png","language":"JavaScript","funding_links":["https://github.com/sponsors/danielesteban"],"categories":[],"sub_categories":[],"readme":"[dudes!](https://dudes.gatunes.com/)\n[![npm-version](https://img.shields.io/npm/v/dudes.svg)](https://www.npmjs.com/package/dudes)\n==\n\n[![screenshot](https://github.com/danielesteban/dudes/raw/master/screenshot.png)](https://dudes.gatunes.com/)\n\n#### Live examples\n\n * [demo](https://dudes.gatunes.com/) | [source](examples/scenes/menu.js) - Menu\n * [demo](https://dudes.gatunes.com/sculpt) | [source](examples/scenes/sculpt.js) - DudeBrush: A VR sculpting tool with import/export \n   * [demo](https://dudes.gatunes.com/sculptserver) | [source](examples/scenes/sculptServer.js) - Multiplayer version\n * [demo](https://dudes.gatunes.com/debug) | [source](examples/scenes/debug.js) - A scene to debug the voxel updates, the physics contact callbacks and the dudes pathfinding.\n   * [demo](https://dudes.gatunes.com/debugserver) | [source](examples/scenes/debugServer.js) - Multiplayer version\n * [demo](https://dudes.gatunes.com/heli) | [source](examples/scenes/heliparty.js) - A helicopter gameplay where you help \"The Chief\" fly dudes up to the party.\n * [demo](https://dudes.gatunes.com/party) | [source](examples/scenes/party.js) - A rave party where you can change the song by showing both thumbs down to \"The Chief\".\n * [demo](https://dudes.gatunes.com/pit) | [source](examples/scenes/pit.js) - A worldgen happy accident\n * [demo](https://dudes.gatunes.com/stress) | [source](examples/scenes/stress.js) - A stress test\n * [demo](https://dudes.gatunes.com/poop) | [source](examples/scenes/poop.js) - Some state-of-the-art poop tech\n\n#### Hello World / Boilerplate\n\n[https://github.com/danielesteban/dudes-boilerplate](https://github.com/danielesteban/dudes-boilerplate.git)\n\n```bash\n# clone the boilerplate\ngit clone https://github.com/danielesteban/dudes-boilerplate.git\ncd dudes-boilerplate\n# install dev dependencies\nnpm install\n# start the dev environment:\nnpm start\n# open http://localhost:8080/ in your browser\n```\n\n#### Multiplayer server\n\nRemix this project on glitch and host it for free:\n\n[https://glitch.com/edit/#!/dudes-server](https://glitch.com/edit/#!/dudes-server)\n\nTo host it on your own server:\n\n```bash\n# install the server\nnpm install -g dudes-server\n# start the server:\ndudes-server\n```\n\nSee the [config options](server#configjson-options) in the server README.\n\n#### Gameplay constructor options\n\n```js\n{\n  world: {\n    // For singleplayer\n    chunkSize: 16,   // Size of the rendering chunks (default: 16)\n    scale: 0.5,      // Scale of the rendering chunks (default: 0.5)\n    width: 256,      // Volume width (should be a multiple of the chunkSize)\n    height: 64,      // Volume height (should be a multiple of the chunkSize)\n    depth: 256,      // Volume depth (should be a multiple of the chunkSize)\n    seaLevel: 6,     // Sea level used in the generation and pathfinding\n    seed: 987654321, // Uint32 seed for the rng. Will use a random one if undefined\n    // Built-in generators\n    generator: 'default', // 'blank', 'default', 'menu', 'debugCity', 'partyBuildings', 'pit'\n    // Custom generator\n    generator: (x, y, z) =\u003e (y \u003c 6 ? { type: 'stone', r: 0xFF, g: 0, b: 0 } : false),\n\n    // For multiplayer\n    server: 'ws://localhost:8081/', // Server url\n\n    // This will be called on every voxels contact if the physics are enabled\n    onContact: (contact) =\u003e {},\n  },\n  dudes: {\n    searchRadius: 64, // The search radius for the pathfinding (default: 64)\n    spawn: {\n      count: 32, // Number of dudes to initially spawn (default: 0)\n      radius: 64, // The search radius for the spawn algorithm (default: 64)\n      // Optional origin for the spawn algorithm.\n      // It defaults to the center of the world if undefined\n      origin: { x: 0, y: 0, z: 0 },\n    },\n    // This will be called on every dudes contact if the physics are enabled\n    onContact: (contact) =\u003e {},\n  },\n  ambient = {\n    range: { from: 0, to: 128 }, // Ambient sounds altitude range (in worldspace)\n    sounds: [\n      {\n        url: '/sounds/sea.ogg', // Public url of the sound\n        from: 0,                // Normalized altitude range\n        to: 0.75,\n      },\n      {\n        url: '/sounds/forest.ogg',\n        from: 0.25,\n        to: 1,\n      },\n    ],\n  },\n  explosionSound: '/sounds/blast.ogg', // Public url of the explosion sound\n  projectileSound: '/sounds/shot.ogg', // Public url of the projectile shooting sound\n  rainSound: '/sounds/rain.ogg',       // Public url of the rain sound\n  audioStream: false, // Request player audio stream (for voice chat) (default: false)\n  explosions: false,  // Enable explosions (default: false)\n  physics: true,      // Enable physics (default: true)\n  projectiles: false, // Enable projectiles (default: false)\n  lightToggle: false, // Enable light toggle UI (default: false)\n  rainToggle: false,  // Enable rain toggle UI (default: false)\n}\n```\n\n#### Gameplay overridable functions\n\n```js\nonLoad(options) {\n  super.onLoad(options);\n  // Do the things you want to do at construction\n  // but require the world to be loaded/generated here\n}\n\nonUnload() {\n  super.onUnload();\n  // Dispose additional geometries/materials you created here\n}\n\nonAnimationTick({ animation, camera, isXR }) {\n  const { hasLoaded } = this;\n  super.onAnimationTick({ animation, camera, isXR });\n  if (!hasLoaded) {\n    return;\n  }\n  // Do input handling and custom animations here\n  // This runs right after the physics and before the rendering\n}\n\nonLocomotionTick({ animation, camera, isXR }) {\n  const { hasLoaded } = this;\n  if (!hasLoaded) {\n    return;\n  }\n  // You can use this to implement a custom locomotion\n  // This runs right before the physics\n}\n```\n\n#### Gameplay helper functions\n\n```js\nspawnProjectile(\n  position = { x: 0, y: 0, z: 0 },\n  impulse = { x: 0, y: 10, z: 0 },\n);\n\nspawnExplosion(\n  position = { x: 0, y: 0, z: 0 },\n  color = new Color(),\n  scale = 0.5\n);\n\nupdateVoxel(\n  brush = {\n    color: new Color(),\n    noise: 0.1,    // color noise\n    type: 'stone', // 'air', 'dirt', 'light', 'stone'\n    shape: 'box',  // 'box', 'sphere'\n    size: 1,       // brush radius\n  },\n  voxel = { x: 0, y: 0, z: 0 }\n);\n```\n\n#### Input state\n\n```js\nonAnimationTick({ animation, camera, isXR }) {\n  const { hasLoaded, player } = this;\n  super.onAnimationTick({ animation, camera, isXR });\n  if (!hasLoaded) {\n    return;\n  }\n\n  // VR controllers input\n  if (isXR) {\n    player.controllers.forEach(({\n      hand, // The hand mesh. Also used to detect controller presence\n      buttons, // Buttons state\n      joystick, // Joystick axes\n      raycaster, // A threejs raycaster with the hand position and direction\n    }) =\u003e {\n      if (hand) {\n        console.log(hand.handedness); // 'left' or 'right'\n        console.log(\n          buttons.trigger, // always true while the trigger is pressed\n          buttons.triggerDown, // only true the first frame after the trigger was pressed\n          buttons.triggerUp, // only true the first frame after the trigger was released\n          buttons.grip, // always true while the grip is pressed\n          buttons.gripDown, // only true the first frame after the grip was pressed\n          buttons.gripUp, // only true the first frame after the grip was released\n          buttons.primary,       // A/X button\n          buttons.primaryDown,\n          buttons.primaryUp,\n          buttons.secondary,     // B/Y button\n          buttons.secondaryDown,\n          buttons.secondaryUp,\n          buttons.forwards,       // Joystick forwards\n          buttons.forwardsDown,\n          buttons.forwardsUp,\n          buttons.backwards,      // Joystick backwards\n          buttons.backwardsDown,\n          buttons.backwardsUp,\n          buttons.leftwards,      // Joystick leftwards\n          buttons.leftwardsDown,\n          buttons.leftwardsUp,\n          buttons.rightwards,     // Joystick rightwards\n          buttons.rightwardsDown,\n          buttons.rightwardsUp\n        );\n      }\n    });\n  }\n\n  // Desktop input\n  if (!isXR) {\n    const {\n      buttons, // Buttons state\n      keyboard, // Keyboard axes\n      raycaster, // A threejs raycaster with the camera position and direction\n    } = player.desktop;\n    console.log(\n      buttons.primary,      // Left mouse button\n      buttons.primaryDown,\n      buttons.primaryUp,\n      buttons.secondary,    // Right mouse button\n      buttons.secondaryDown,\n      buttons.secondaryUp,\n      buttons.tertiary,     // Middle mouse button (or F)\n      buttons.tertiaryDown,\n      buttons.tertiaryUp,\n      buttons.view,         // V\n      buttons.viewDown,\n      buttons.viewUp\n    );\n  }\n}\n```\n\n#### Physics\n\n```js\n// A box\nmesh.physics = {\n  shape: 'box',\n  width: 1,\n  height: 1,\n  depth: 1,\n};\n\n// A capsule\nmesh.physics = {\n  shape: 'capsule',\n  radius: 0.5,\n  height: 1,\n};\n\n// A sphere\nmesh.physics = {\n  shape: 'sphere',\n  radius: 0.5,\n};\n\n// A plane\nmesh.physics = {\n  shape: 'plane',\n  constant: 0,\n  normal: { x: 0, y: 1, z: 0 },\n};\n\nphysics.addMesh(\n  mesh, // A threejs Mesh (or InstancedMesh) with a physics definition\n  {\n    // Optional flags\n    isKinematic: true,\n    isTrigger: true, // This will call mesh.onContact on every contact\n  }\n);\n\nphysics.addConstraint(\n  mesh, // Mesh that was already added to the physics with physics.addMesh\n  instance = 0, // For instanced meshes\n  options = {\n    type: 'p2p',\n    mesh: anotherMesh, // Another mesh already added to the physics\n    pivotInA: { x: 0, y: 0, z: 0 },\n    pivotInB: { x: 0, y: 0, z: 0 },\n  },\n);\n\nphysics.addConstraint(\n  mesh, // Mesh that was already added to the physics with physics.addMesh\n  instance = 0, // For instanced meshes\n  options = {\n    type: 'hinge',\n    mesh: anotherMesh, // Another mesh already added to the physics\n    pivotInA: { x: 0, y: 0, z: 0 },\n    pivotInB: { x: 0, y: 0, z: 0 },\n    axisInA: { x: 0, y: 1, z: 0 },\n    axisInB: { x: 0, y: 1, z: 0 },\n    friction: true, // simulate friction using an angular motor\n    limits: { // optional limits\n      low: 0,\n      high: Math.PI * 2,\n    },\n  },\n);\n\nphysics.applyImpulse(\n  mesh, // Mesh that was already added to the physics with physics.addMesh\n  instance = 0, // For instanced meshes\n  impulse = { x: 0, y: 10, z: 0 },\n);\n\nphysics.setTransform(\n  mesh, // Mesh that was already added to the physics with physics.addMesh\n  instance = 0, // For instanced meshes\n  position = { x: 0, y: 0, z: 0 },\n  rotation = { x: 0, y: 0, z: 0, w: 1 },\n);\n\nphysics.raycast(\n  origin = { x: 0, y: 0, z: 0 }, // ray origin\n  direction = { x: 0, y: 0, z: -1 }, // ray direction\n  mask = 1, // collision mask (-1: ALL | 1: STATIC | 2: DYNAMIC | 4: KINEMATIC)\n  far = 64\n);\n```\n\n#### Voxelizer\n\n```js\nimport { Voxelizer } from 'dudes';\n\nconst voxelizer = new Voxelizer({\n  maxWidth: 256,\n  maxHeight: 32,\n  maxDepth: 256,\n});\nvoxelizer.voxelize({\n  scale: 0.5,\n  offset: {\n    x: voxelizer.world.width * -0.5,\n    y: -1,\n    z: voxelizer.world.depth * -0.5,\n  },\n  generator: (x, y, z) =\u003e {\n    const r = Math.sqrt((x - 128.5) ** 2 + ((y - 16.5) * 2) ** 2 + (z - 128.5) ** 2);\n    if (\n      r \u003e 32 \u0026\u0026 r \u003c 64 \u0026\u0026 y \u003c 16\n    ) {\n      return {\n        type: 'stone',\n        r: 0xBB - Math.random() * 0x33,\n        g: 0x66 - Math.random() * 0x33,\n        b: 0x44 - Math.random() * 0x22,\n      };\n    }\n    return false;\n  },\n})\n  .then((mesh) =\u003e {\n    this.add(mesh);\n  });\n```\n\n#### Engine dev dependencies\n\nTo build the C code, you'll need to install LLVM:\n\n * Win: [https://chocolatey.org/packages/llvm](https://chocolatey.org/packages/llvm)\n * Mac: [https://formulae.brew.sh/formula/llvm](https://formulae.brew.sh/formula/llvm)\n * Linux: [https://releases.llvm.org/download.html](https://releases.llvm.org/download.html)\n\nOn the first build, it will complain about a missing file that you can get here:\n[libclang_rt.builtins-wasm32-wasi-12.0.tar.gz](https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/libclang_rt.builtins-wasm32-wasi-12.0.tar.gz). Just put it on the same path that the error specifies and you should be good to go.\n\nTo build [wasi-libc](https://github.com/WebAssembly/wasi-libc), you'll need to install [GNU make](https://chocolatey.org/packages/make).\n\n#### Engine dev environment\n\n```bash\n# clone this repo and it's submodules\ngit clone --recursive https://github.com/danielesteban/dudes.git\ncd dudes\n# build wasi-libc\ncd vendor/wasi-libc \u0026\u0026 make -j8 \u0026\u0026 cd ../..\n# install dev dependencies\nnpm install\n# start the dev environment:\nnpm start\n# open http://localhost:8080/ in your browser\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielesteban%2Fdudes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielesteban%2Fdudes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielesteban%2Fdudes/lists"}