Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions docs/stl.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,19 @@ For questions, problems, or improvements, contact me on Twitter [@probabletrain]
For this guide I'm using Blender, which is excellent and free 3D modeling software.
If you're already handy with Blender this section might be all you need. Otherwise, see the next section for screenshots and more detail.

**Version**: Constructing the model requires boolean operations on objects with co-planar surfaces. The only way I got this to properly work in Blender was using version **2.79b** which still has the `Carve` solver for the boolean modifier. I couldn't get it to work on 2.8+. According to the Blender forums, proper boolean operations with coplanar surfaces is in development, so this may change.

- Import `sea` and `domain` and extrude them both in the z axis. Subtract `sea` from `domain` using boolean difference. Make sure to use `Carve` as the solver.
- Import `sea` and `domain` and extrude them both in the z axis. Subtract `sea` from `domain` using boolean difference.
- If using a Blender version below 2.8, you may need to use `Carve` as the solver.
- Import `coastline` and extrude in the z axis. Union with `domain`.
- Import `river` and extrude in z axis. Subtract from `domain` with boolean difference.
- If you don't care about accurate roads, import `blocks` and `buildings` and place them on top of `domain`.
- If you want proper road geometry, import `roads`. Extract in z, select all, press `p -> By loose parts`. This splits each road into its own object.
- If you want proper road geometry, import `roads`. Extrude in z, select all, press `p -> By loose parts`. This splits each road into its own object.
- Enable `Bool Tools` addon
- Select `domain` and all of the separate road objects, with `domain` as the active object.
- Use `BoolTools -> difference` to subtract roads from `domain`.
- If the resulting mesh is fine, move on. Otherwise, select one of the top faces, press `shift+G -> normal` or `shift+G -> coplanar` to select all the top faces. Move these to a separate object with `p -> selection`. Delete the old object, extrude the new object.
- Import roads again, extrude in z, use it to create the roads by filling the gaps in `domain`. Control the road depth with the z location of `roads`.
- Import `domain` again, use it to create the sea and river. Control the depth with its z value.


## Detailed Blender Overview

Note that this isn't intended to be a first-time Blender tutorial, and may assume basic Blender knowledge.
Expand Down
14 changes: 9 additions & 5 deletions src/ts/model_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ enum ModelGeneratorStates {

export default class ModelGenerator {
private readonly groundLevel = 20; // Thickness of groundMesh

// Using THREE.extrudeGeometry with depth = 0 flips normals
// Specifying a small minimum extrusion depth prevents issues with Boolean modifiers in Blender
private readonly minExtrudeDepth = 0.01;

private readonly exportSTL = require('threejs-export-stl');
private resolve: (blob: any) => void = b => {};
Expand Down Expand Up @@ -72,28 +76,28 @@ export default class ModelGenerator {
return false;
}
case ModelGeneratorStates.SUBTRACT_OCEAN: {
const seaLevelMesh = this.polygonToMesh(this.ground, 0);
const seaLevelMesh = this.polygonToMesh(this.ground, this.minExtrudeDepth);
this.threeToBlender(seaLevelMesh);
const seaLevelSTL = this.exportSTL.fromMesh(seaLevelMesh);
this.zip.file("model/domain.stl", seaLevelSTL);

const seaMesh = this.polygonToMesh(this.sea, 0);
const seaMesh = this.polygonToMesh(this.sea, this.minExtrudeDepth);
this.threeToBlender(seaMesh);
const seaMeshSTL = this.exportSTL.fromMesh(seaMesh);
this.zip.file("model/sea.stl", seaMeshSTL);
this.setState(ModelGeneratorStates.ADD_COASTLINE);
break;
}
case ModelGeneratorStates.ADD_COASTLINE: {
const coastlineMesh = this.polygonToMesh(this.coastline, 0);
const coastlineMesh = this.polygonToMesh(this.coastline, this.minExtrudeDepth);
this.threeToBlender(coastlineMesh);
const coastlineSTL = this.exportSTL.fromMesh(coastlineMesh);
this.zip.file("model/coastline.stl", coastlineSTL);
this.setState(ModelGeneratorStates.SUBTRACT_RIVER);
break;
}
case ModelGeneratorStates.SUBTRACT_RIVER: {
const riverMesh = this.polygonToMesh(this.river, 0);
const riverMesh = this.polygonToMesh(this.river, this.minExtrudeDepth);
this.threeToBlender(riverMesh);
const riverSTL = this.exportSTL.fromMesh(riverMesh);
this.zip.file("model/river.stl", riverSTL);
Expand All @@ -114,7 +118,7 @@ export default class ModelGenerator {
}

const road = this.polygonsToProcess.pop();
const roadsMesh = this.polygonToMesh(road, 0);
const roadsMesh = this.polygonToMesh(road, this.minExtrudeDepth);
this.roadsGeometry.merge(roadsMesh.geometry as THREE.Geometry, this.groundMesh.matrix);
break;
}
Expand Down