example: bottle

This commit is contained in:
L-Nafaryus 2024-03-30 00:31:28 +05:00
parent 3677e0e5b1
commit bf3468676c
Signed by: L-Nafaryus
GPG Key ID: 582F8B0866B294A1
4 changed files with 4306 additions and 19 deletions

3817
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,12 @@ edition = "2021"
authors = ["L-Nafaryus <l.nafaryus@elnafo.ru"]
[dependencies]
bevy = "0.13.1"
cxx = "1.0.120"
glam = { version = "0.24.0", features = ["bytemuck"] }
nalgebra = "0.32.4"
opencascade = { version = "0.2.0", default-features = false }
opencascade = { version = "0.2.0", default-features = true }
opencascade-sys = "0.2.0"
[workspace]
members = []

477
examples/bottle.rs Normal file
View File

@ -0,0 +1,477 @@
use cxx::UniquePtr;
use opencascade_sys::ffi::{
compute_normals, cylinder_to_surface, ellipse_to_HandleGeom2d_Curve, ellipse_value,
gp_Ax2_ctor, gp_Ax2d_ctor, gp_Ax3_from_gp_Ax2, gp_DZ, gp_Dir2d_ctor, gp_OX,
handle_geom_plane_location, new_HandleGeomCurve_from_HandleGeom_TrimmedCurve,
new_HandleGeomPlane_from_HandleGeomSurface, new_list_of_shape, new_point, new_point_2d,
new_transform, new_vec, shape_list_append_face, type_name, write_stl, BRepAlgoAPI_Fuse_ctor,
BRepBuilderAPI_MakeEdge_CurveSurface2d, BRepBuilderAPI_MakeEdge_HandleGeomCurve,
BRepBuilderAPI_MakeFace_wire, BRepBuilderAPI_MakeWire_ctor, BRepBuilderAPI_MakeWire_edge_edge,
BRepBuilderAPI_MakeWire_edge_edge_edge, BRepBuilderAPI_Transform_ctor,
BRepFilletAPI_MakeFillet_ctor, BRepLibBuildCurves3d, BRepMesh_IncrementalMesh_ctor,
BRepOffsetAPI_MakeThickSolid_ctor, BRepOffsetAPI_ThruSections_ctor,
BRepPrimAPI_MakeCylinder_ctor, BRepPrimAPI_MakePrism_ctor, BRep_Builder_ctor,
BRep_Builder_upcast_to_topods_builder, BRep_Tool_Surface, BRep_Tool_Triangulation, DynamicType,
ExplorerCurrentShape, GCE2d_MakeSegment_point_point, GC_MakeArcOfCircle_Value,
GC_MakeArcOfCircle_point_point_point, GC_MakeSegment_Value, GC_MakeSegment_point_point,
Geom2d_Ellipse_ctor, Geom2d_TrimmedCurve_ctor, Geom_CylindricalSurface_ctor,
HandleGeom2d_TrimmedCurve_to_curve, Handle_Poly_Triangulation_Get, MakeThickSolidByJoin,
Poly_Triangulation_Node, Poly_Triangulation_Normal, Poly_Triangulation_UV, StlAPI_Writer_ctor,
TColgp_Array1OfDir_ctor, TopAbs_Orientation, TopAbs_ShapeEnum, TopExp_Explorer_ctor,
TopLoc_Location_Transformation, TopLoc_Location_ctor, TopoDS_Compound_as_shape,
TopoDS_Compound_ctor, TopoDS_Face, TopoDS_Face_to_owned, TopoDS_Shape_to_owned,
TopoDS_cast_to_edge, TopoDS_cast_to_face, TopoDS_cast_to_wire,
};
use bevy::prelude::*;
use bevy::render::{
mesh::{Indices, VertexAttributeValues},
render_asset::RenderAssetUsages,
render_resource::PrimitiveTopology,
};
#[derive(Debug)]
pub struct BMesh {
pub vertices: Vec<[f32; 3]>, //Vec<Vec<f64>>,
pub uv: Vec<[f32; 2]>, //Vec<Vec<f64>>,
pub normals: Vec<[f32; 3]>, //Vec<Vec<f64>>,
pub indices: Vec<usize>, //Vec<usize>,
}
pub fn bottle_mesh() -> BMesh {
let height = 70.0;
let width = 50.0;
let thickness = 30.0;
// Define the points making up the bottle's profile.
let point_1 = new_point(-width / 2.0, 0.0, 0.0);
let point_2 = new_point(-width / 2.0, -thickness / 4.0, 0.0);
let point_3 = new_point(0.0, -thickness / 2.0, 0.0);
let point_4 = new_point(width / 2.0, -thickness / 4.0, 0.0);
let point_5 = new_point(width / 2.0, 0.0, 0.0);
// Define the arcs and segments of the profile.
let arc = GC_MakeArcOfCircle_point_point_point(&point_2, &point_3, &point_4);
let segment_1 = GC_MakeSegment_point_point(&point_1, &point_2);
let segment_2 = GC_MakeSegment_point_point(&point_4, &point_5);
let mut edge_1 = BRepBuilderAPI_MakeEdge_HandleGeomCurve(
&new_HandleGeomCurve_from_HandleGeom_TrimmedCurve(&GC_MakeSegment_Value(&segment_1)),
);
let mut edge_2 = BRepBuilderAPI_MakeEdge_HandleGeomCurve(
&new_HandleGeomCurve_from_HandleGeom_TrimmedCurve(&GC_MakeArcOfCircle_Value(&arc)),
);
let mut edge_3 = BRepBuilderAPI_MakeEdge_HandleGeomCurve(
&new_HandleGeomCurve_from_HandleGeom_TrimmedCurve(&GC_MakeSegment_Value(&segment_2)),
);
let mut wire = BRepBuilderAPI_MakeWire_edge_edge_edge(
edge_1.pin_mut().Edge(),
edge_2.pin_mut().Edge(),
edge_3.pin_mut().Edge(),
);
let x_axis = gp_OX();
let mut transform = new_transform();
transform.pin_mut().set_mirror_axis(x_axis);
// We're calling Shape() here instead of Wire(), hope that's okay.
let mut brep_transform =
BRepBuilderAPI_Transform_ctor(wire.pin_mut().Shape(), &transform, false);
let mirrored_shape = brep_transform.pin_mut().Shape();
let mirrored_wire = TopoDS_cast_to_wire(mirrored_shape);
let mut make_wire = BRepBuilderAPI_MakeWire_ctor();
make_wire.pin_mut().add_wire(wire.pin_mut().Wire());
make_wire.pin_mut().add_wire(mirrored_wire);
let wire_profile = make_wire.pin_mut().Wire();
let mut face_profile = BRepBuilderAPI_MakeFace_wire(wire_profile, false);
let prism_vec = new_vec(0.0, 0.0, height);
// We're calling Shape here instead of Face(), hope that's also okay.
let mut body =
BRepPrimAPI_MakePrism_ctor(face_profile.pin_mut().Shape(), &prism_vec, false, true);
let mut make_fillet = BRepFilletAPI_MakeFillet_ctor(body.pin_mut().Shape());
let mut edge_explorer =
TopExp_Explorer_ctor(body.pin_mut().Shape(), TopAbs_ShapeEnum::TopAbs_EDGE);
while edge_explorer.More() {
let edge = TopoDS_cast_to_edge(edge_explorer.Current());
make_fillet.pin_mut().add_edge(thickness / 12.0, edge);
edge_explorer.pin_mut().Next();
}
let body_shape = make_fillet.pin_mut().Shape();
// Make the bottle neck
let neck_location = new_point(0.0, 0.0, height);
let neck_axis = gp_DZ();
let neck_coord_system = gp_Ax2_ctor(&neck_location, neck_axis);
let neck_radius = thickness / 4.0;
let neck_height = height / 10.0;
let mut cylinder = BRepPrimAPI_MakeCylinder_ctor(&neck_coord_system, neck_radius, neck_height);
let cylinder_shape = cylinder.pin_mut().Shape();
let mut fuse_neck = BRepAlgoAPI_Fuse_ctor(body_shape, cylinder_shape);
let body_shape = fuse_neck.pin_mut().Shape();
// Make the bottle hollow
let mut face_explorer = TopExp_Explorer_ctor(body_shape, TopAbs_ShapeEnum::TopAbs_FACE);
let mut z_max = -1.0;
let mut top_face: Option<UniquePtr<TopoDS_Face>> = None;
while face_explorer.More() {
let shape = ExplorerCurrentShape(&face_explorer);
let face = TopoDS_cast_to_face(&shape);
let surface = BRep_Tool_Surface(face);
let dynamic_type = DynamicType(&surface);
let name = type_name(dynamic_type);
if name == "Geom_Plane" {
let plane_handle = new_HandleGeomPlane_from_HandleGeomSurface(&surface);
let plane_location = handle_geom_plane_location(&plane_handle);
let plane_z = plane_location.Z();
if plane_z > z_max {
z_max = plane_z;
top_face = Some(TopoDS_Face_to_owned(face));
}
}
face_explorer.pin_mut().Next();
}
let top_face = top_face.unwrap();
let mut faces_to_remove = new_list_of_shape();
shape_list_append_face(faces_to_remove.pin_mut(), &top_face);
let mut solid_maker = BRepOffsetAPI_MakeThickSolid_ctor();
MakeThickSolidByJoin(
solid_maker.pin_mut(),
body_shape,
&faces_to_remove,
-thickness / 50.0,
1.0e-3,
);
let body_shape = solid_maker.pin_mut().Shape();
// Create the threading
let cylinder_axis = gp_Ax3_from_gp_Ax2(&neck_coord_system);
let cylinder_1 = Geom_CylindricalSurface_ctor(&cylinder_axis, neck_radius * 0.99);
let cylinder_1 = cylinder_to_surface(&cylinder_1);
let cylinder_2 = Geom_CylindricalSurface_ctor(&cylinder_axis, neck_radius * 1.05);
let cylinder_2 = cylinder_to_surface(&cylinder_2);
let a_pnt = new_point_2d(std::f64::consts::TAU, neck_height / 2.0);
let a_dir = gp_Dir2d_ctor(std::f64::consts::TAU, neck_height / 4.0);
let thread_axis = gp_Ax2d_ctor(&a_pnt, &a_dir);
let a_major = std::f64::consts::TAU;
let a_minor = neck_height / 10.0;
let ellipse_1 = Geom2d_Ellipse_ctor(&thread_axis, a_major, a_minor);
let ellipse_1_handle = ellipse_to_HandleGeom2d_Curve(&ellipse_1);
let ellipse_2 = Geom2d_Ellipse_ctor(&thread_axis, a_major, a_minor / 4.0);
let ellipse_2_handle = ellipse_to_HandleGeom2d_Curve(&ellipse_2);
let arc_1 = Geom2d_TrimmedCurve_ctor(&ellipse_1_handle, 0.0, std::f64::consts::PI);
let arc_1 = HandleGeom2d_TrimmedCurve_to_curve(&arc_1);
let arc_2 = Geom2d_TrimmedCurve_ctor(&ellipse_2_handle, 0.0, std::f64::consts::PI);
let arc_2 = HandleGeom2d_TrimmedCurve_to_curve(&arc_2);
let ellipse_point_1 = ellipse_value(&ellipse_1, 0.0);
let ellipse_point_2 = ellipse_value(&ellipse_1, std::f64::consts::PI);
let thread_segment = GCE2d_MakeSegment_point_point(&ellipse_point_1, &ellipse_point_2);
let thread_segment = HandleGeom2d_TrimmedCurve_to_curve(&thread_segment);
let mut edge_1_on_surface_1 = BRepBuilderAPI_MakeEdge_CurveSurface2d(&arc_1, &cylinder_1);
let mut edge_2_on_surface_1 =
BRepBuilderAPI_MakeEdge_CurveSurface2d(&thread_segment, &cylinder_1);
let mut edge_1_on_surface_2 = BRepBuilderAPI_MakeEdge_CurveSurface2d(&arc_2, &cylinder_2);
let mut edge_2_on_surface_2 =
BRepBuilderAPI_MakeEdge_CurveSurface2d(&thread_segment, &cylinder_2);
let mut threading_wire_1 = BRepBuilderAPI_MakeWire_edge_edge(
edge_1_on_surface_1.pin_mut().Edge(),
edge_2_on_surface_1.pin_mut().Edge(),
);
let mut threading_wire_2 = BRepBuilderAPI_MakeWire_edge_edge(
edge_1_on_surface_2.pin_mut().Edge(),
edge_2_on_surface_2.pin_mut().Edge(),
);
// TODO - does calling Shape() work here instead of Wire()?
BRepLibBuildCurves3d(threading_wire_1.pin_mut().Shape());
BRepLibBuildCurves3d(threading_wire_2.pin_mut().Shape());
let is_solid = true;
let mut threading_loft = BRepOffsetAPI_ThruSections_ctor(is_solid);
threading_loft
.pin_mut()
.AddWire(threading_wire_1.pin_mut().Wire());
threading_loft
.pin_mut()
.AddWire(threading_wire_2.pin_mut().Wire());
threading_loft.pin_mut().CheckCompatibility(false);
let threading_shape = threading_loft.pin_mut().Shape();
// Build the resulting compound
let mut compound = TopoDS_Compound_ctor();
let builder = BRep_Builder_ctor();
let builder = BRep_Builder_upcast_to_topods_builder(&builder);
builder.MakeCompound(compound.pin_mut());
let mut compound_shape = TopoDS_Compound_as_shape(compound);
builder.Add(compound_shape.pin_mut(), body_shape);
builder.Add(compound_shape.pin_mut(), threading_shape);
let final_shape = compound_shape;
let mut triangulation = BRepMesh_IncrementalMesh_ctor(&final_shape, 0.01);
let mut vertices = vec![];
let mut uvs = vec![];
let mut normals = vec![];
let mut indices = vec![];
let mut face_explorer = TopExp_Explorer_ctor(
triangulation.pin_mut().Shape(),
TopAbs_ShapeEnum::TopAbs_FACE,
);
while face_explorer.More() {
let shape = ExplorerCurrentShape(&face_explorer);
let face = TopoDS_cast_to_face(&shape);
let mut location = TopLoc_Location_ctor();
let triangulation_handle = BRep_Tool_Triangulation(face, location.pin_mut());
let triangulation = Handle_Poly_Triangulation_Get(&triangulation_handle).unwrap();
let index_offset = vertices.len();
let face_point_count = triangulation.NbNodes();
for i in 1..=face_point_count {
let mut point = Poly_Triangulation_Node(triangulation, i);
point
.pin_mut()
.Transform(&TopLoc_Location_Transformation(&location));
vertices.push([point.X() as f32, point.Y() as f32, point.Z() as f32]);
}
let mut u_min = f64::INFINITY;
let mut v_min = f64::INFINITY;
let mut u_max = f64::NEG_INFINITY;
let mut v_max = f64::NEG_INFINITY;
for i in 1..=(face_point_count) {
let uv = Poly_Triangulation_UV(triangulation, i);
let (u, v) = (uv.X(), uv.Y());
u_min = u_min.min(u);
v_min = v_min.min(v);
u_max = u_max.max(u);
v_max = v_max.max(v);
uvs.push([u as f32, v as f32]);
}
// Normalize the newly added UV coordinates.
for uv in &mut uvs[index_offset..(index_offset + face_point_count as usize)] {
uv[0] = (uv[0] - u_min as f32) / (u_max - u_min) as f32;
uv[1] = (uv[1] - v_min as f32) / (v_max - v_min) as f32;
if face.Orientation() != TopAbs_Orientation::TopAbs_FORWARD {
uv[0] = 1.0 - uv[0];
}
}
// Add in the normals.
// TODO(bschwind) - Use `location` to transform the normals.
let normal_array = TColgp_Array1OfDir_ctor(0, face_point_count);
compute_normals(face, &triangulation_handle);
// TODO(bschwind) - Why do we start at 1 here?
for i in 1..(normal_array.Length() as usize) {
let normal = Poly_Triangulation_Normal(triangulation, i as i32);
normals.push([normal.X() as f32, normal.Y() as f32, normal.Z() as f32]);
}
for i in 1..=triangulation.NbTriangles() {
let triangle = triangulation.Triangle(i);
if face.Orientation() == TopAbs_Orientation::TopAbs_FORWARD {
indices.push(index_offset + triangle.Value(1) as usize - 1);
indices.push(index_offset + triangle.Value(2) as usize - 1);
indices.push(index_offset + triangle.Value(3) as usize - 1);
} else {
indices.push(index_offset + triangle.Value(3) as usize - 1);
indices.push(index_offset + triangle.Value(2) as usize - 1);
indices.push(index_offset + triangle.Value(1) as usize - 1);
}
}
face_explorer.pin_mut().Next();
}
BMesh {
vertices,
uv: uvs,
normals,
indices,
}
}
#[derive(Component)]
struct CustomUV;
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
) {
// Import the custom texture.
//let custom_texture_handle: Handle<Image> = asset_server.load("textures/array_texture.png");
// Create and save a handle to the mesh.
let cube_mesh_handle: Handle<Mesh> = meshes.add(create_bottle_mesh());
// Render the mesh with the custom texture using a PbrBundle, add the marker.
commands.spawn((
PbrBundle {
mesh: cube_mesh_handle,
material: materials.add(StandardMaterial {
//base_color_texture: Some(custom_texture_handle),
..default()
}),
..default()
},
CustomUV,
));
// Transform for the camera and lighting, looking at (0,0,0) (the position of the mesh).
let camera_and_light_transform =
Transform::from_xyz(35.0, 35.0, 35.0).looking_at(Vec3::ZERO, Vec3::Y);
// Camera in 3D space.
commands.spawn(Camera3dBundle {
transform: camera_and_light_transform,
..default()
});
// Light up the scene.
commands.spawn(PointLightBundle {
transform: camera_and_light_transform,
..default()
});
// Text to describe the controls.
commands.spawn(
TextBundle::from_section(
"Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation",
TextStyle {
font_size: 20.0,
..default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
}),
);
}
fn input_handler(
keyboard_input: Res<ButtonInput<KeyCode>>,
mesh_query: Query<&Handle<Mesh>, With<CustomUV>>,
mut meshes: ResMut<Assets<Mesh>>,
mut query: Query<&mut Transform, With<CustomUV>>,
time: Res<Time>,
) {
if keyboard_input.just_pressed(KeyCode::Space) {
let mesh_handle = mesh_query.get_single().expect("Query not successful");
//let mesh = meshes.get_mut(mesh_handle).unwrap();
//toggle_texture(mesh);
}
if keyboard_input.pressed(KeyCode::KeyX) {
for mut transform in &mut query {
transform.rotate_x(time.delta_seconds() / 1.2);
}
}
if keyboard_input.pressed(KeyCode::KeyY) {
for mut transform in &mut query {
transform.rotate_y(time.delta_seconds() / 1.2);
}
}
if keyboard_input.pressed(KeyCode::KeyZ) {
for mut transform in &mut query {
transform.rotate_z(time.delta_seconds() / 1.2);
}
}
if keyboard_input.pressed(KeyCode::KeyR) {
for mut transform in &mut query {
transform.look_to(Vec3::NEG_Z, Vec3::Y);
}
}
}
fn create_bottle_mesh() -> Mesh {
let mesh = bottle_mesh();
// Keep the mesh data accessible in future frames to be able to mutate it in toggle_texture.
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
)
.with_inserted_attribute(
Mesh::ATTRIBUTE_POSITION,
// Each array is an [x, y, z] coordinate in local space.
// Meshes always rotate around their local [0, 0, 0] when a rotation is applied to their Transform.
// By centering our mesh around the origin, rotating the mesh preserves its center of mass.
mesh.vertices,
)
// Set-up UV coordinates to point to the upper (V < 0.5), "dirt+grass" part of the texture.
// Take a look at the custom image (assets/textures/array_texture.png)
// so the UV coords will make more sense
// Note: (0.0, 0.0) = Top-Left in UV mapping, (1.0, 1.0) = Bottom-Right in UV mapping
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, mesh.uv)
// For meshes with flat shading, normals are orthogonal (pointing out) from the direction of
// the surface.
// Normals are required for correct lighting calculations.
// Each array represents a normalized vector, which length should be equal to 1.0.
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, mesh.normals)
// Create the triangles out of the 24 vertices we created.
// To construct a square, we need 2 triangles, therefore 12 triangles in total.
// To construct a triangle, we need the indices of its 3 defined vertices, adding them one
// by one, in a counter-clockwise order (relative to the position of the viewer, the order
// should appear counter-clockwise from the front of the triangle, in this case from outside the cube).
// Read more about how to correctly build a mesh manually in the Bevy documentation of a Mesh,
// further examples and the implementation of the built-in shapes.
.with_inserted_indices(Indices::U32(
mesh.indices.into_iter().map(|x| x as u32).collect(),
))
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, input_handler)
.run();
}

View File

@ -51,19 +51,24 @@
devShells = forAllSystems (system: {
default = let
pkgs = nixpkgsFor.${system};
#db_host = "";
db_name = "hpr";
db_user = "hpr";
db_password = "test";
db_path = "temp/hpr";
in pkgs.mkShell {
buildInputs = [
buildInputs = with pkgs; [
fenix.packages.${system}.complete.toolchain
pkgs.cargo-watch
pkgs.mold-wrapped
pkgs.cmake
pkgs.opencascade-occt
cargo-watch
mold-wrapped
cmake
xorg.libXi xorg.libX11 xorg.libXcursor
lld libxkbcommon pkg-config alsa-lib libudev-zero
libGL
vulkan-tools vulkan-headers vulkan-loader vulkan-validation-layers
];
shellHook = ''
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${
with pkgs; lib.makeLibraryPath [ libGL xorg.libX11 xorg.libXi xorg.libXcursor libxkbcommon vulkan-loader ]
}"
'';
};
hpr = crane.lib.${system}.devShell {
checks = self.checks.${system};