オブジェクトを作る < rusterizer-from-dots
from 点を描くところから始めるRust製ソフトウェアラスタライザ
オブジェクトを作る < Rust製ラスタライザ
https://gyazo.com/ed28ed847bfd69926b66ad880c726e23
モデルデータを定義する。
Actorに複数枚のポリゴンを合わせられるようにする。
https://gyazo.com/de1a6672197e3f2c2f3e28cd985f5913
定義を全体的に書き換え
code:actor.rs
pub struct Actor {
pub polygons:Vec<Polygon>,
pub position:Vec4,
pub axis:Vec4,
pub theta:f64
}
pub struct Polygon {
pub vertices: Vec4; 3,
pub color: Vec4; 3,
}
impl Actor {
pub fn model_conversion(&self) -> Mat4x4 {
Mat4x4::translate(&self.position)
* Mat4x4::rotation(&self.axis, self.theta)
}
}
cameraのsnapshot関数を書き換え...
code:rs
pub fn snapshot(&mut self, canvas:&mut Canvas, actors:&Vec<Actor>) {
let perspective = self.perspective_conversion();
let view = self.view_conversion();
let pv = perspective*view;
for actor in actors {
let model = actor.model_conversion();
let pvm = &pv * &model;
for polygon in &actor.polygons {
let projected = [
Vec4Project::new(&pvm * &polygon.vertices0).into_screen(&canvas.size()),
Vec4Project::new(&pvm * &polygon.vertices1).into_screen(&canvas.size()),
Vec4Project::new(&pvm * &polygon.vertices2).into_screen(&canvas.size()),
];
canvas.draw_triangle(projected, &polygon.color);
}
}
}
そして、mainのコードを次のように書き換え
code:main.rs
let world:Vec<Actor> = vec![
Actor{
polygons: vec![
Polygon{
vertices:Vec4::newpoint(1., 2., 3.), Vec4::newpoint(1., -2., 0.), Vec4::newpoint(-1., -1., -3.),
color: red.clone(), blue.clone(), red.clone()
},
Polygon{
vertices:Vec4::newpoint(1., 2., -3.), Vec4::newpoint(-1., -1., 3.), Vec4::newpoint(1., -2., 0.),
color: green.clone(), blue.clone(), green.clone()
},
],
position: Vec4::newpoint(0., 0., 0.),
axis: Vec4::newvec(0., 0., 0.),
theta: 0.0,
}
];
https://gyazo.com/ab4591c737d147a18e83e51646f70b5a
サンプルモデルとして、四面体を作ろう!appbird.icon
単位球に内接する4点は次のとおり
$ v_1 = (0, 1, 0)
$ v_2 = (\cos0, -1/3, \sin 0)
$ v_3 = (\cos\frac{2}{3}\pi, -1/3, \sin \frac{2}{3}\pi)
$ v_4 = (\cos\frac{4}{3}\pi, -1/3, \sin \frac{4}{3}\pi)
これらで面を構成してみる。
$ P_{234}, P_{134}, P_{124}, P_{123}
これらの面の重心ベクトルを求めた上で、重心を中心に面を0.95倍に縮小する。
ポリゴンを生成するコード
code:rs
pub fn tetrahedron(vert_color:Vec4; 3) -> Vec<Polygon> {
let vectors = [
Vec4::newpoint(0., 1., 0.),
Vec4::newpoint(f64::cos(0.), -1./3., f64::sin(0.)),
Vec4::newpoint(f64::cos(2.*PI/3.), -1./3., f64::sin(2.*PI/3.)),
Vec4::newpoint(f64::cos(4.*PI/3.), -1./3., f64::sin(4.*PI/3.)),
];
let indecies: [usize; 3; 4] = [
1, 2, 3,
0, 2, 3,
0, 1, 3,
0, 1, 2,
];
let mut polygons: Vec<Polygon> = vec![];
for index_array in indecies {
let mut vertex = [
vectors[index_array0].clone(),
vectors[index_array1].clone(),
vectors[index_array2].clone()
];
let g = (&(&vertex0 + &vertex1) + (&vertex2)) / 3.;
for i in 0..3 {
vertexi = &((&vertexi - &g) * 0.95) + &g;
}
polygons.push(
Polygon {
vertices: vertex,
color: vert_color.clone()
}
);
};
polygons
}
https://gyazo.com/275fa618c89c252f6437cb31b19c7274
https://gyazo.com/de1a6672197e3f2c2f3e28cd985f5913
最後に思いっきりいっぱい生成してぶっ飛ばしてみないか?appbird.icon
Actorにvelとacc, angular_vel, angular_accに追加する
角度$ \bm \thetaのデータ表現はどうする?
いちばんいいのはクォータニオンを持つことなんだけど、後で導入する機会を設けたいので、一旦より単純な実装を
$ \bm \thetaをそのままオブジェクトに持たせるか
となるとモデル座標変換行列は次のような実装になる
code:rs
impl Vec4 {
pub fn norm3d(&self) -> f64 {
let (x, y, z) = (self.x(), self.y(), self.z());
return f64::sqrt(x*x + y*y + z*z);
}
...
}
code:rs
impl Actor {
pub fn model_conversion(&self) -> Mat4x4 {
let t = self.theta.norm3d();
Mat4x4::translate(&self.position)
* Mat4x4::rotation(&(&self.theta / t), t)
}
...
}
毎フレームsqrtを計算することになるけど避けられないだろうか...。
pos, thetaの更新式を書く
updateメソッドを用意appbird.icon
deltatimeを渡すようにする。
+=が実装できていなかったので追加する
code:rs
impl ops::AddAssign<&Vec4> for Vec4 {
fn add_assign(&mut self, rhs: &Self) {
for i in 0..4 { self.ei += rhs.ei; }
}
}
impl ops::AddAssign<Vec4> for Vec4 {
fn add_assign(&mut self, rhs: Self) {
for i in 0..4 { self.ei += rhs.ei; }
}
}
(なんかもうちょっといい書き方はないか...?)
code:world/actor.rs
pub fn update(&mut self, dt:f64) -> () {
self.position += &self.velocity * dt;
self.velocity += &self.acc * dt;
self.theta += &self.omega * dt;
self.omega += &self.angacc * dt;
}
オブジェクトの削除ってどうしたっけ
いまWorldがActorのリストになってるけど、それでいいんだっけ
削除予定かどうかを持っておく設定にしとこうかと思ったけど
destroyしたあとその領域ってどう詰めるべきか
WorldやとO(N)だし...
std::setで持つべき?いやでもなぁ...。
GPT君に聞いてみると
Free listを持っておいて開放済みの場所を覚えておこう
まぁ....そうなるよなぁ...appbird.icon
容量そんなかっつかつってわけでもないし、こうするか
しっかしスレッドを複数立てて並行でやるときがちょっと怖いなぁ....
インデックス変わりはするけど、そもそもworldのインデックスを当てにするべきでないという話がある
まぁインデックスの対応表を持ち続ければいいかな
Rustのswap_removeを使えばええか
https://natsutan.hatenablog.com/entry/2018/11/19/182233
code:rs
let mut world = vec![];
let mut free_queue = VecDeque::<usize>::new();
// ....
while canvas.update()? {
for (idx, actor) in world.iter_mut().enumerate() {
actor.update(dt);
if actor.is_terminated() { to_be_destroyed.push_back(idx); }
}
// NOTE: to_be_destroyedは単調増加な数列になっているので、swap_removeするときの添え字ずれが起こっているかチェックする必要はない(常に添え字がずれている。)
for idx in &to_be_destroyed {
world.swap_remove(idx - deleted_count);
}
}
こりゃだめだ!バグだ!
swap_removeしていると、一番後ろの要素が前に持ってこられる可能性がある
idxが最後の要素を指している場合うまくいかなくなるのでは
---> 逆順から読めば依存関係が絡むことはない
code:rs
while let Some(idx) = to_be_destroyed.pop() {
world.swap_remove(idx);
}
actor.is_terminatedを実装する。
内部状態にterminatedを持たせる必要がある
でもこれは外部からpublicにするにはちょっと...
newコンストラクタを作る
code:world/actor.rs
impl Actor {
pub fn new(
polygon:Vec<Polygon>
) -> Actor {
let zerovec = Vec4::newvec(0., 0., 0.);
Actor {
polygons: polygon,
position: Vec4::newpoint(0., 0., 0.),
velocity: zerovec.clone(),
acc: zerovec.clone(),
theta: Vec4::newvec(2.*PI, 0., 0.),
omega: zerovec.clone(),
angacc: zerovec.clone(),
scale: Vec4::newvec(1., 1., 1.),
created_at: Instant::now(),
terminated: false
}
}
...
code:util/random.rs
use rand::{rngs::ThreadRng, Rng};
use std::f64::consts::PI;
use crate::util::Vec4;
impl Vec4 {
pub fn cone_zplus(rng:&mut ThreadRng) -> Vec4 {
let theta = rng.random_range(0.0..2.*PI);
Vec4::newvec(f64::cos(theta), f64::sin(theta), 1.).normalized3d()
}
pub fn choice_in_sphere(rng:&mut ThreadRng) -> Vec4 {
let mut x = 1.;
let mut y = 1.;
let mut z = 1.;
while x*x + y*y + z*z > 1. {
x = rng.random_range(0.0..1.) + 1e-6;
y = rng.random_range(0.0..1.) + 1e-6;
z = rng.random_range(0.0..1.) + 1e-6;
}
Vec4::newvec(x, y, z).normalized3d()
}
}
ちょっとchoice_in_sphereの実装はLas Vegas法のそれなんであんまり実用的ではないが、一様に選ぶのが面倒くさいのでこうするappbird.icon
というわけでワールドのセットアップ。
まず、オブジェクトがずっと生きてちゃまずいので、3秒たったらオブジェクトを倒しておく
code:rs
pub fn update(&mut self, dt:f64) -> () {
self.position += &self.velocity * dt;
self.velocity += &self.acc * dt;
self.theta += &self.omega * dt;
self.omega += &self.angacc * dt;
if self.created_at.elapsed().as_secs_f64() > 3.0 { self.terminate(); }
}
code:rs
let tetras = [
tetrahedron(red.clone(), red.clone(), blue.clone()),
tetrahedron(green.clone(), green.clone(), blue.clone()),
tetrahedron(red.clone(), blue.clone(), green.clone())
];
let mut rng = rand::rng();
let mut world = vec![];
let mut to_be_destroyed = Vec::<usize>::new();
let mut count_tetra = 0;
let tetra_interval = 0.13;
while canvas.update()? {
// deltatimeの計測など
let t = from_start.elapsed_as_sec();
let dt = from_prev_frame.elapsed_as_sec();
from_prev_frame.reset();
from_prev_frame.start();
// カメラ位置の設定
camera.position = Vec4::newpoint(r*f64::cos(k*t), r*f64::sin(k*t), 0.) ;
camera.look = Vec4::newvec(-f64::cos(k*t), -f64::sin(k*t), 0.) ;
camera.up = Vec4::newvec(0., 0., 1.);
// オブジェクト追加処理
if t > tetra_interval * (count_tetra as f64) {
count_tetra += 1;
let tetra = tetras.choose(&mut rng).unwrap().clone();
let mut actor1 = Actor::new(tetra);
let theta_fire = 2.*PI / 3. * t;
actor1.velocity = Vec4::newvec(f64::cos(theta_fire), f64::sin(theta_fire), 1.).normalized3d() * 15.;
actor1.acc = Vec4::newvec(0., 0., -12.);
actor1.theta = Vec4::choice_in_sphere(&mut rng) * PI;
actor1.omega = Vec4::choice_in_sphere(&mut rng) * rng.random_range(0. .. 4.*PI);
let mut actor2 = actor1.clone();
actor2.velocity = -actor2.velocity;
world.push(actor1); world.push(actor2);
}
// Actorを更新していく
for (idx, actor) in world.iter_mut().enumerate() {
actor.update(dt);
if actor.is_terminated() { to_be_destroyed.push(idx); }
}
while let Some(idx) = to_be_destroyed.pop() {
world.swap_remove(idx);
}
}
// DONE: Actorに速度・加速度をつける!
// deltatime, Actorへの機能追加
camera.snapshot(canvas, &world);
}
https://gyazo.com/ed28ed847bfd69926b66ad880c726e23
いいね
でもオブジェクトが突然消えていくのはなんか妙だappbird.icon
フォグをかけて違和感の内容にしよう
バーテックスシェーダーとフラグメントシェーダを考えてみる。