use crate::ecs::light::components::LightType;
use crate::ecs::world::{GLOBAL_TRANSFORM, LIGHT, WATER, World};
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use nalgebra_glm::{Mat4, Vec3};
use wgpu::util::DeviceExt;

const MAX_BODIES: usize = 16;
const UNIFORM_STRIDE: u64 = 768;

#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct WaterUniform {
    render_vp: [[f32; 4]; 4],
    inv_render_vp: [[f32; 4]; 4],
    cur_vp: [[f32; 4]; 4],
    prev_vp: [[f32; 4]; 4],
    model: [[f32; 4]; 4],
    extents: [f32; 4],
    shallow_color: [f32; 4],
    deep_color: [f32; 4],
    foam_color: [f32; 4],
    wave: [f32; 4],
    wave_dir_time: [f32; 4],
    params: [f32; 4],
    sun_dir: [f32; 4],
    sun_color: [f32; 4],
    camera_pos: [f32; 4],
    resolution: [f32; 4],
    fog_color: [f32; 4],
    fog_params: [f32; 4],
    pad: [[f32; 4]; 15],
}

pub struct WaterPass {
    pipeline: wgpu::RenderPipeline,
    bind_group_layout: wgpu::BindGroupLayout,
    uniform_buffer: wgpu::Buffer,
    linear_sampler: wgpu::Sampler,
    point_sampler: wgpu::Sampler,
    ibl_sampler: wgpu::Sampler,
    fallback_environment_view: wgpu::TextureView,
    vertex_buffer: wgpu::Buffer,
    index_buffer: wgpu::Buffer,
    index_count: u32,
    grid_resolution: u32,
    refraction_texture: wgpu::Texture,
    refraction_view: wgpu::TextureView,
    depth_copy_texture: wgpu::Texture,
    depth_copy_view: wgpu::TextureView,
    copy_size: (u32, u32),
    color_format: wgpu::TextureFormat,
}

fn build_grid(resolution: u32) -> (Vec<[f32; 2]>, Vec<u32>) {
    let segments = resolution.max(2);
    let count = segments + 1;
    let mut vertices = Vec::with_capacity((count * count) as usize);
    for z in 0..count {
        for x in 0..count {
            let fx = x as f32 / segments as f32 * 2.0 - 1.0;
            let fz = z as f32 / segments as f32 * 2.0 - 1.0;
            vertices.push([fx, fz]);
        }
    }
    let mut indices = Vec::with_capacity((segments * segments * 6) as usize);
    for z in 0..segments {
        for x in 0..segments {
            let top_left = z * count + x;
            let top_right = top_left + 1;
            let bottom_left = top_left + count;
            let bottom_right = bottom_left + 1;
            indices.push(top_left);
            indices.push(bottom_left);
            indices.push(top_right);
            indices.push(top_right);
            indices.push(bottom_left);
            indices.push(bottom_right);
        }
    }
    (vertices, indices)
}

fn create_fallback_environment_view(device: &wgpu::Device) -> wgpu::TextureView {
    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some("Water Fallback Environment"),
        size: wgpu::Extent3d {
            width: 1,
            height: 1,
            depth_or_array_layers: 6,
        },
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: wgpu::TextureFormat::Rgba16Float,
        usage: wgpu::TextureUsages::TEXTURE_BINDING,
        view_formats: &[],
    });
    texture.create_view(&wgpu::TextureViewDescriptor {
        label: Some("Water Fallback Environment View"),
        dimension: Some(wgpu::TextureViewDimension::Cube),
        ..Default::default()
    })
}

fn create_copy_textures(
    device: &wgpu::Device,
    color_format: wgpu::TextureFormat,
    width: u32,
    height: u32,
) -> (
    wgpu::Texture,
    wgpu::TextureView,
    wgpu::Texture,
    wgpu::TextureView,
) {
    let refraction_texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some("Water Refraction Texture"),
        size: wgpu::Extent3d {
            width: width.max(1),
            height: height.max(1),
            depth_or_array_layers: 1,
        },
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: color_format,
        usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
        view_formats: &[],
    });
    let refraction_view = refraction_texture.create_view(&wgpu::TextureViewDescriptor::default());

    let depth_copy_texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some("Water Depth Copy Texture"),
        size: wgpu::Extent3d {
            width: width.max(1),
            height: height.max(1),
            depth_or_array_layers: 1,
        },
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: wgpu::TextureFormat::Depth32Float,
        usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
        view_formats: &[],
    });
    let depth_copy_view = depth_copy_texture.create_view(&wgpu::TextureViewDescriptor::default());

    (
        refraction_texture,
        refraction_view,
        depth_copy_texture,
        depth_copy_view,
    )
}

impl WaterPass {
    pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
        let shader = crate::render::wgpu::shader_compose::compile_wgsl(
            device,
            "water.wgsl",
            include_str!("../../shaders/water.wgsl"),
        );

        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("Water Bind Group Layout"),
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: true,
                        min_binding_size: wgpu::BufferSize::new(UNIFORM_STRIDE),
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 1,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        view_dimension: wgpu::TextureViewDimension::D2,
                        multisampled: false,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 2,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        sample_type: wgpu::TextureSampleType::Depth,
                        view_dimension: wgpu::TextureViewDimension::D2,
                        multisampled: false,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 3,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 4,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 5,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        view_dimension: wgpu::TextureViewDimension::Cube,
                        multisampled: false,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 6,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                    count: None,
                },
            ],
        });

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Water Pipeline Layout"),
            bind_group_layouts: &[Some(&bind_group_layout)],
            immediate_size: 0,
        });

        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Water Pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vs_main"),
                buffers: &[wgpu::VertexBufferLayout {
                    array_stride: 8,
                    step_mode: wgpu::VertexStepMode::Vertex,
                    attributes: &[wgpu::VertexAttribute {
                        format: wgpu::VertexFormat::Float32x2,
                        offset: 0,
                        shader_location: 0,
                    }],
                }],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fs_main"),
                targets: &[
                    Some(wgpu::ColorTargetState {
                        format: color_format,
                        blend: None,
                        write_mask: wgpu::ColorWrites::ALL,
                    }),
                    Some(wgpu::ColorTargetState {
                        format: wgpu::TextureFormat::Rg16Float,
                        blend: None,
                        write_mask: wgpu::ColorWrites::ALL,
                    }),
                ],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: wgpu::TextureFormat::Depth32Float,
                depth_write_enabled: Some(true),
                depth_compare: Some(wgpu::CompareFunction::GreaterEqual),
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview_mask: None,
            cache: None,
        });

        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Water Uniform Buffer"),
            size: UNIFORM_STRIDE * MAX_BODIES as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Water Linear Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
            ..Default::default()
        });
        let point_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Water Point Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
            ..Default::default()
        });
        let ibl_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Water IBL Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::MipmapFilterMode::Linear,
            ..Default::default()
        });
        let fallback_environment_view = create_fallback_environment_view(device);

        let grid_resolution = 192;
        let (vertices, indices) = build_grid(grid_resolution);
        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Water Vertex Buffer"),
            contents: bytemuck::cast_slice(&vertices),
            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
        });
        let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Water Index Buffer"),
            contents: bytemuck::cast_slice(&indices),
            usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
        });
        let index_count = indices.len() as u32;

        let (refraction_texture, refraction_view, depth_copy_texture, depth_copy_view) =
            create_copy_textures(device, color_format, 1, 1);

        Self {
            pipeline,
            bind_group_layout,
            uniform_buffer,
            linear_sampler,
            point_sampler,
            ibl_sampler,
            fallback_environment_view,
            vertex_buffer,
            index_buffer,
            index_count,
            grid_resolution,
            refraction_texture,
            refraction_view,
            depth_copy_texture,
            depth_copy_view,
            copy_size: (1, 1),
            color_format,
        }
    }

    fn rebuild_grid(&mut self, device: &wgpu::Device, resolution: u32) {
        if resolution == self.grid_resolution {
            return;
        }
        let (vertices, indices) = build_grid(resolution);
        self.vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Water Vertex Buffer"),
            contents: bytemuck::cast_slice(&vertices),
            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
        });
        self.index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Water Index Buffer"),
            contents: bytemuck::cast_slice(&indices),
            usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
        });
        self.index_count = indices.len() as u32;
        self.grid_resolution = resolution;
    }
}

fn query_sun(world: &World) -> (Vec3, Vec3) {
    let mut direction = Vec3::new(0.35, 0.85, 0.4).normalize();
    let mut color = Vec3::new(1.0, 0.96, 0.9);
    let mut found = false;
    world
        .core
        .query()
        .with(LIGHT | GLOBAL_TRANSFORM)
        .iter(|_, table, index| {
            if found {
                return;
            }
            let light = &table.light[index];
            if light.light_type == LightType::Directional {
                let matrix = table.global_transform[index].0;
                let forward = Vec3::new(matrix[(0, 2)], matrix[(1, 2)], matrix[(2, 2)]);
                let mut toward = forward.normalize();
                if toward.y < 0.0 {
                    toward = -toward;
                }
                direction = toward;
                color = light.color * light.intensity.max(0.0);
                found = true;
            }
        });
    (direction, color)
}

impl PassNode<World> for WaterPass {
    fn name(&self) -> &str {
        "water_pass"
    }

    fn reads(&self) -> Vec<&str> {
        vec![]
    }

    fn writes(&self) -> Vec<&str> {
        vec![]
    }

    fn reads_writes(&self) -> Vec<&str> {
        vec!["color", "depth", "velocity"]
    }

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, World>,
    ) -> crate::render::wgpu::rendergraph::Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
    > {
        if !context.is_pass_enabled() || !context.configs.resources.render_settings.water_enabled {
            return Ok(context.into_sub_graph_commands());
        }

        let world = context.configs;

        let mut bodies: Vec<(crate::ecs::water::components::Water, Mat4)> = Vec::new();
        world
            .core
            .query()
            .with(WATER | GLOBAL_TRANSFORM)
            .iter(|_, table, index| {
                if bodies.len() >= MAX_BODIES {
                    return;
                }
                let body = table.water[index].clone();
                if body.enabled {
                    bodies.push((body, table.global_transform[index].0));
                }
            });

        if bodies.is_empty() {
            return Ok(context.into_sub_graph_commands());
        }

        let camera = match crate::ecs::camera::queries::query_active_camera_matrices(world) {
            Some(matrices) => matrices,
            None => return Ok(context.into_sub_graph_commands()),
        };

        let max_resolution = bodies
            .iter()
            .map(|(body, _)| body.tessellation.clamp(4, 512))
            .max()
            .unwrap_or(192);
        self.rebuild_grid(context.device, max_resolution);

        let scene_color = context.get_texture("color")?;
        let scene_depth = context.get_texture("depth")?;
        let width = scene_color.width();
        let height = scene_color.height();
        if (width, height) != self.copy_size {
            let (refraction_texture, refraction_view, depth_copy_texture, depth_copy_view) =
                create_copy_textures(context.device, self.color_format, width, height);
            self.refraction_texture = refraction_texture;
            self.refraction_view = refraction_view;
            self.depth_copy_texture = depth_copy_texture;
            self.depth_copy_view = depth_copy_view;
            self.copy_size = (width, height);
        }

        context.encoder.copy_texture_to_texture(
            wgpu::TexelCopyTextureInfo {
                texture: scene_color,
                mip_level: 0,
                origin: wgpu::Origin3d::ZERO,
                aspect: wgpu::TextureAspect::All,
            },
            wgpu::TexelCopyTextureInfo {
                texture: &self.refraction_texture,
                mip_level: 0,
                origin: wgpu::Origin3d::ZERO,
                aspect: wgpu::TextureAspect::All,
            },
            wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
        );
        context.encoder.copy_texture_to_texture(
            wgpu::TexelCopyTextureInfo {
                texture: scene_depth,
                mip_level: 0,
                origin: wgpu::Origin3d::ZERO,
                aspect: wgpu::TextureAspect::DepthOnly,
            },
            wgpu::TexelCopyTextureInfo {
                texture: &self.depth_copy_texture,
                mip_level: 0,
                origin: wgpu::Origin3d::ZERO,
                aspect: wgpu::TextureAspect::DepthOnly,
            },
            wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
        );

        let render_vp = camera.projection * camera.view;
        let render_vp_array: [[f32; 4]; 4] = render_vp.into();
        let inv_render_vp: [[f32; 4]; 4] = render_vp
            .try_inverse()
            .unwrap_or_else(Mat4::identity)
            .into();
        let cur_vp: [[f32; 4]; 4] =
            Mat4::from(world.resources.renderer_state.view_projection).into();
        let prev_vp: [[f32; 4]; 4] =
            Mat4::from(world.resources.renderer_state.prev_view_projection).into();
        let camera_position = camera.camera_position;

        let (sun_direction, sun_color) = query_sun(world);

        let (fog_color, fog_params) = match &world.resources.renderer_state.active_view.fog {
            Some(fog) => (
                [fog.color[0], fog.color[1], fog.color[2], 1.0],
                [fog.start, fog.end, 0.0, 0.0],
            ),
            None => ([0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]),
        };

        let time = world.resources.window.timing.uptime_milliseconds as f32 / 1000.0;
        let prev_time = time - world.resources.window.timing.delta_time.max(0.0);

        for (index, (body, transform)) in bodies.iter().enumerate() {
            let direction = Vec3::new(
                body.wave_direction_radians.cos(),
                0.0,
                body.wave_direction_radians.sin(),
            );

            let model: [[f32; 4]; 4] = (*transform).into();

            let uniform = WaterUniform {
                render_vp: render_vp_array,
                inv_render_vp,
                cur_vp,
                prev_vp,
                model,
                extents: [
                    body.half_extents.x,
                    body.half_extents.y,
                    body.specular_strength,
                    0.0,
                ],
                shallow_color: [
                    body.shallow_color[0],
                    body.shallow_color[1],
                    body.shallow_color[2],
                    body.depth_fade_distance,
                ],
                deep_color: [
                    body.deep_color[0],
                    body.deep_color[1],
                    body.deep_color[2],
                    body.edge_foam_distance,
                ],
                foam_color: [
                    body.foam_color[0],
                    body.foam_color[1],
                    body.foam_color[2],
                    body.foam_amount,
                ],
                wave: [
                    body.wave_amplitude,
                    body.wave_steepness,
                    body.wave_length,
                    body.wave_speed,
                ],
                wave_dir_time: [direction.x, direction.z, time, prev_time],
                params: [
                    body.roughness,
                    body.fresnel_power,
                    body.reflection_strength,
                    body.refraction_strength,
                ],
                sun_dir: [sun_direction.x, sun_direction.y, sun_direction.z, 0.0],
                sun_color: [sun_color.x, sun_color.y, sun_color.z, 0.0],
                camera_pos: [camera_position.x, camera_position.y, camera_position.z, 1.0],
                resolution: [width.max(1) as f32, height.max(1) as f32, 0.0, 0.0],
                fog_color,
                fog_params,
                pad: [[0.0; 4]; 15],
            };
            context.queue.write_buffer(
                &self.uniform_buffer,
                index as u64 * UNIFORM_STRIDE,
                bytemuck::bytes_of(&uniform),
            );
        }

        let environment_view = world
            .resources
            .ibl_views
            .prefiltered_view
            .as_ref()
            .unwrap_or(&self.fallback_environment_view);

        let bind_group = context
            .device
            .create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Water Bind Group"),
                layout: &self.bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
                            buffer: &self.uniform_buffer,
                            offset: 0,
                            size: wgpu::BufferSize::new(UNIFORM_STRIDE),
                        }),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::TextureView(&self.refraction_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(&self.depth_copy_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::Sampler(&self.linear_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 4,
                        resource: wgpu::BindingResource::Sampler(&self.point_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: wgpu::BindingResource::TextureView(environment_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 6,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                ],
            });

        let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
        let (velocity_view, velocity_load, velocity_store) =
            context.get_color_attachment("velocity")?;
        let (depth_view, depth_load, depth_store) = context.get_depth_attachment("depth")?;

        {
            let mut render_pass = context
                .encoder
                .begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: Some("Water Pass"),
                    color_attachments: &[
                        Some(wgpu::RenderPassColorAttachment {
                            view: color_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: color_load,
                                store: color_store,
                            },
                            depth_slice: None,
                        }),
                        Some(wgpu::RenderPassColorAttachment {
                            view: velocity_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: velocity_load,
                                store: velocity_store,
                            },
                            depth_slice: None,
                        }),
                    ],
                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                        view: depth_view,
                        depth_ops: Some(wgpu::Operations {
                            load: depth_load,
                            store: depth_store,
                        }),
                        stencil_ops: None,
                    }),
                    timestamp_writes: None,
                    occlusion_query_set: None,
                    multiview_mask: None,
                });

            render_pass.set_pipeline(&self.pipeline);
            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
            for index in 0..bodies.len() {
                let offset = index as u32 * UNIFORM_STRIDE as u32;
                render_pass.set_bind_group(0, &bind_group, &[offset]);
                render_pass.draw_indexed(0..self.index_count, 0, 0..1);
            }
        }

        Ok(context.into_sub_graph_commands())
    }
}
