vkf_model.f90 Source File


This file depends on

sourcefile~~vkf_model.f90~~EfferentGraph sourcefile~vkf_model.f90 vkf_model.f90 sourcefile~vkf_frames.f90 vkf_frames.f90 sourcefile~vkf_model.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_layer.f90 vkf_layer.f90 sourcefile~vkf_model.f90->sourcefile~vkf_layer.f90 sourcefile~vkf_math.f90 vkf_math.f90 sourcefile~vkf_model.f90->sourcefile~vkf_math.f90 sourcefile~vkf_pick.f90 vkf_pick.f90 sourcefile~vkf_model.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_layer.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_math.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_follower.f90 vkf_follower.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_follower.f90 sourcefile~vkf_geometry.f90 vkf_geometry.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_geometry.f90 sourcefile~vkf_text.f90 vkf_text.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_text.f90 sourcefile~vkf_trajectory.f90 vkf_trajectory.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkf_pick.f90->sourcefile~vkf_math.f90 sourcefile~vkf_follower.f90->sourcefile~vkf_math.f90 sourcefile~vkf_follower.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkf_geometry.f90->sourcefile~vkf_layer.f90 sourcefile~vkf_geometry.f90->sourcefile~vkf_math.f90 sourcefile~vkf_text.f90->sourcefile~vkf_layer.f90 sourcefile~vkf_text.f90->sourcefile~vkf_math.f90 sourcefile~vkf_files.f90 vkf_files.f90 sourcefile~vkf_text.f90->sourcefile~vkf_files.f90 sourcefile~vkf_trajectory.f90->sourcefile~vkf_math.f90

Files dependent on this one

sourcefile~~vkf_model.f90~~AfferentGraph sourcefile~vkf_model.f90 vkf_model.f90 sourcefile~vkframes.f90 vkframes.f90 sourcefile~vkframes.f90->sourcefile~vkf_model.f90

Source Code

!> 3D models: a model file (3ds, obj, glTF, ... through vsgXchange) in a
!> frame, as OpenFrames' Model.
!>
!> The model is placed in its frame by its own transform:
!> translate(model_position) * rotate(model_attitude) * scale(model_scale) *
!> translate(-model_pivot), so e.g. the pivot can be the model's center
!> ([[model_bound]]) and the scale can size it.
module vkf_model

    use, intrinsic :: iso_c_binding, only: c_ptr, c_null_ptr, c_associated, c_int
    use vkf_math, only: wp, quat_identity, quat_normalize, frame_matrix, identity_matrix
    use vkf_frames, only: ReferenceFrame, PickHit, pick_ellipsoid
    use vkf_pick, only: PickCamera
    use vkf_layer, only: vkf_model_read, vkf_node_bound, vkf_transform_create, vkf_transform_set_matrix, &
                         vkf_node_add_child

    implicit none

    private

    !> A model (see the module documentation).
    type, extends(ReferenceFrame), public :: Model
        private
        character(len=:), allocatable :: file               !! the model file (unallocated: none)
        type(c_ptr) :: model_ = c_null_ptr                  !! the loaded model's node
        real(wp) :: model_position_(3) = 0.0_wp             !! where the pivot is in the frame
        real(wp) :: model_attitude_(4) = quat_identity      !! attitude in the frame (quaternion [x, y, z, w])
        real(wp) :: scale_(3) = 1.0_wp                      !! scale along the model's x, y, z
        real(wp) :: pivot_(3) = 0.0_wp                      !! the model's point at model_position_ (model coordinates)
        real(wp) :: bound_center(3) = 0.0_wp                !! the model's bounding sphere: center (model coordinates)
        real(wp) :: bound_radius = 0.0_wp                   !! the model's bounding sphere: radius (0: unknown)
        type(c_ptr) :: model_node = c_null_ptr               !! transform of the model within the frame
    contains
        procedure :: set_model, share_model, model_file, has_model, model_bound
        procedure :: set_model_position, model_position
        procedure :: set_model_attitude, model_attitude
        procedure :: set_model_scale, model_scale
        procedure :: set_model_pivot, model_pivot
        procedure :: frame_info => model_frame_info
        procedure :: bound => model_frame_bound
        procedure :: pick => model_pick
        procedure :: build_contents => model_build_contents
    end type Model

    !> The Model constructor: `Model(...)` makes one (see [[new_model]] for its
    !> arguments).
    interface Model
        module procedure new_model
    end interface Model

contains

    !> A model frame named `name` (decorations of color `color`), with the
    !> model file `file` if given (see [[set_model]]).
    function new_model(name, file, color) result(m)
        character(len=*), intent(in) :: name            !! the frame's name
        character(len=*), intent(in), optional :: file  !! model file (3ds, obj, glTF, ...)
        real(wp), intent(in), optional :: color(:)      !! [r, g, b] or [r, g, b, a] (0 to 1)
        type(Model) :: m
        logical :: ok
        m%ReferenceFrame = ReferenceFrame(name, color)
        if (present(file)) ok = m%set_model(file)
    end function new_model

    !> Read the model file (now, so its bound is known). Returns false if it
    !> could not be read. Set it before a window first shows the model.
    logical function set_model(self, file) result(ok)
        class(Model), intent(inout) :: self
        character(len=*), intent(in) :: file                !! model file (3ds, obj, glTF, ...)
        type(c_ptr) :: node
        node = vkf_model_read(file, len(file, kind=c_int))
        ok = c_associated(node)
        if (.not. ok) return
        self%file = file
        self%model_ = node
        if (vkf_node_bound(node, self%bound_center, self%bound_radius) /= 0) then
            self%bound_center = 0.0_wp
            self%bound_radius = 0.0_wp
        end if
    end function set_model

    !> Use the model already read by `other` (no second copy in memory).
    logical function share_model(self, other) result(ok)
        class(Model), intent(inout) :: self
        class(Model), intent(in) :: other                   !! a model with its model read
        ok = c_associated(other%model_)
        if (.not. ok) return
        self%model_ = other%model_
        self%file = other%file
        self%bound_center = other%bound_center
        self%bound_radius = other%bound_radius
    end function share_model

    !> The model file ('' if none).
    function model_file(self) result(file)
        class(Model), intent(in) :: self
        character(len=:), allocatable :: file
        file = ''
        if (allocated(self%file)) file = self%file
    end function model_file

    !> True if a model was read (or shared).
    pure logical function has_model(self)
        class(Model), intent(in) :: self
        has_model = c_associated(self%model_)
    end function has_model

    !> The model's bounding sphere, in the model's own coordinates (before its transform).
    subroutine model_bound(self, center, radius)
        class(Model), intent(in) :: self
        real(wp), intent(out) :: center(3), radius          !! the bounding sphere, model coordinates
        center = self%bound_center
        radius = self%bound_radius
    end subroutine model_bound

    !> Where the model's pivot is placed in its frame (frame coordinates).
    subroutine set_model_position(self, position)
        class(Model), intent(inout) :: self
        real(wp), intent(in) :: position(3)                 !! frame coordinates
        self%model_position_ = position
        call update_model_transform(self)
    end subroutine set_model_position

    !> Where the model's pivot is placed in its frame (frame coordinates).
    pure function model_position(self) result(p)
        class(Model), intent(in) :: self
        real(wp) :: p(3)
        p = self%model_position_
    end function model_position

    !> Attitude of the model in its frame (quaternion [x, y, z, w]).
    subroutine set_model_attitude(self, q)
        class(Model), intent(inout) :: self
        real(wp), intent(in) :: q(4)                        !! quaternion [x, y, z, w] (normalized here)
        self%model_attitude_ = quat_normalize(q)
        call update_model_transform(self)
    end subroutine set_model_attitude

    !> Attitude of the model in its frame (quaternion [x, y, z, w]).
    pure function model_attitude(self) result(q)
        class(Model), intent(in) :: self
        real(wp) :: q(4)
        q = self%model_attitude_
    end function model_attitude

    !> Scale along the model's x, y and z axes.
    subroutine set_model_scale(self, scale)
        class(Model), intent(inout) :: self
        real(wp), intent(in) :: scale(3)                    !! along the model's x, y and z
        self%scale_ = scale
        call update_model_transform(self)
    end subroutine set_model_scale

    !> Scale along the model's x, y and z axes.
    pure function model_scale(self) result(s)
        class(Model), intent(in) :: self
        real(wp) :: s(3)
        s = self%scale_
    end function model_scale

    !> The point of the model (in its own coordinates) placed at model_position,
    !> about which it is rotated and scaled.
    subroutine set_model_pivot(self, pivot)
        class(Model), intent(inout) :: self
        real(wp), intent(in) :: pivot(3)    !! (model coordinates)
        self%pivot_ = pivot
        call update_model_transform(self)
    end subroutine set_model_pivot

    !> The model's pivot, in its own coordinates (see [[set_model_pivot]]).
    pure function model_pivot(self) result(p)
        class(Model), intent(in) :: self
        real(wp) :: p(3)
        p = self%pivot_
    end function model_pivot

    !> The model's bounding sphere in the frame (through the model transform).
    subroutine model_frame_bound(self, center, radius)
        class(Model), intent(in) :: self
        real(wp), intent(out) :: center(3), radius          !! the bounding sphere, frame coordinates (scene units)
        real(wp) :: m(4,4)
        m = frame_matrix(self%model_position_, self%model_attitude_, self%scale_, self%pivot_)
        center = matmul(m(1:3, 1:3), self%bound_center) + m(1:3, 4)
        radius = self%bound_radius * maxval(abs(self%scale_))
    end subroutine model_frame_bound

    !> The kind of frame: 'Model'.
    function model_frame_info(self) result(info)
        class(Model), intent(in) :: self
        character(len=:), allocatable :: info
        info = 'Model'
    end function model_frame_info

    !> The model under the model transform.
    subroutine model_build_contents(self, viewer, group)
        class(Model), intent(inout) :: self
        type(c_ptr), intent(in) :: viewer   !! a window's viewer handle (null: headless)
        type(c_ptr), intent(in) :: group    !! the group node to build under
        if (.not. c_associated(self%model_)) return
        self%model_node = vkf_transform_create()
        call update_model_transform(self)
        if (vkf_node_add_child(self%model_node, self%model_) /= 0 .or. vkf_node_add_child(group, self%model_node) /= 0) &
            write(*, '(a)') 'vkframes: Model '//self%name()//': could not build the scene'
    end subroutine model_build_contents

    !> Give the model transform to its node (once built).
    subroutine update_model_transform(self)
        type(Model), intent(inout) :: self
        if (.not. c_associated(self%model_node)) return
        if (vkf_transform_set_matrix(self%model_node, &
                frame_matrix(self%model_position_, self%model_attitude_, self%scale_, self%pivot_)) /= 0) &
            write(*, '(a)') 'vkframes: Model '//self%name()//': transform failed'
    end subroutine update_model_transform

    !> Picked on the model's bounding sphere (not its triangles); else at the
    !> frame's origin.
    subroutine model_pick(self, cam, to_root, hits, n)
        class(Model), intent(in), target :: self
        type(PickCamera), intent(in) :: cam                 !! the cursor's ray and the pick's settings
        real(wp), intent(in) :: to_root(4,4)                !! the frame's transform to the root frame
        type(PickHit), allocatable, intent(inout) :: hits(:)  !! hits so far (grown as needed)
        integer, intent(inout) :: n                         !! number of hits in hits
        real(wp) :: c(3), r, m(4,4)
        call self%bound(c, r)
        if (self%contents_shown() .and. r > 0.0_wp) then
            m = identity_matrix()
            if (pick_ellipsoid(self, cam, to_root, c, m(1:3, 1:3), [r, r, r], .false., hits, n)) return
        end if
        call self%ReferenceFrame%pick(cam, to_root, hits, n)
    end subroutine model_pick

end module vkf_model