線分の交差判定を行う。二次元画像の場合、結果がピクセル単位になるため座標の四捨五入が必要。
#pragma once
template<typename ScalarT> struct Point { ScalarT x, y; Point(const ScalarT X, const ScalarT Y) :x(X), y(Y) {} template<typename T> Point(const Point<T>& src) { x = src.x; y = src.y; } Point() {} }; template<typename ScalarT> struct LineSegment { Point<ScalarT> s, e; template<typename T> LineSegment(const Point<T>& S, const Point<T>& E) :s(S), e(E) {} template<typename T> LineSegment(const LineSegment<T>& LS):s(LS.s),e(LS.e){} LineSegment() {} };
using Pointd = Point<double>; using LineSegmentd = LineSegment<double>;
//! @brief 線分の交点を求める //! @param [out] cross 交点座標の格納先 //! @param [in] L1 線分1 //! @param [in] L2 線分2 //! @retval true 交差している //! @retval false 交差していない bool isCross(Pointd* cross, const LineSegmentd& L1, const LineSegmentd& L2) { const double Ax = L1.s.x; const double Ay = L1.s.y; const double Bx = L1.e.x; const double By = L1.e.y; const double Cx = L2.s.x; const double Cy = L2.s.y; const double Dx = L2.e.x; const double Dy = L2.e.y; //分子 const double s_numerator = (Cx - Ax)*(Dy - Cy) - (Cy - Ay)*(Dx - Cx); //分母 const double s_denominator = (Bx - Ax)*(Dy - Cy) - (By - Ay)*(Dx - Cx); //分子 const double t_numerator = (Ax - Cx)*(By - Ay) - (Ay - Cy)*(Bx - Ax); //分母 const double t_denominator = (Dx - Cx)*(By - Ay) - (Dy - Cy)*(Bx - Ax); const double s = s_numerator / s_denominator; const double t = t_numerator / t_denominator; if ( 0 <= s && s <= 1 && 0 <= t && t <= 1 ) { cross->x = Ax + s * (Bx - Ax); cross->y = Ay + s * (By - Ay); return true; } return false; }
using Pointi = Point<int>; using LineSegmenti = LineSegment<int>;
//! @brief ピクセル単位を想定して線分の交差判定を行う bool isCrossPixel(Pointi* P, const LineSegmenti a, const LineSegmenti b) { LineSegmentd da(a); LineSegmentd db(b); Pointd c; if (isCross(&c, da, db) == false) return false; P->x = (int)round(c.x);// 四捨五入 P->y = (int)round(c.y);// 四捨五入 return true; }
以前書いたBresenhamとデータ用にNByteDataを使用している。
#pragma warning(disable:4996) #include <iostream> #include <vector> #include <random> // 乱数用 #include "Bresenham.hpp" #include "NByteData.hpp" #include "LineCross.hpp" void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax );
int main() { using ucrgb = NByteData<3>; int width = 200; int height = 200; std::vector<ucrgb> image; ucrgb Black = ucrgb{ 0,0,0 }; ucrgb White = ucrgb{ 255,255,255 }; ucrgb Red = ucrgb{ 255,0,0 }; ucrgb Blue = ucrgb{ 0,0,255 }; image.resize(width*height, White); // 疑似乱数 std::mt19937 mt(2); // 0~5の範囲の一様乱数作成用 // float型が欲しいなら std::uniform_real_distribution<> std::uniform_int_distribution<> idistriW(0, width-1); std::uniform_int_distribution<> idistriH(0, height-1); //赤い線分 const LineSegmenti A( Pointi(20,80),Pointi(190,123) ); Bresenham(image.data(), width, height, A.s.x, A.s.y, A.e.x, A.e.y, Red); for (size_t i = 0; i < 10; i++) { int x1 = idistriW(mt); int y1 = idistriH(mt); int x2 = idistriW(mt); int y2 = idistriH(mt); //黒い線分 LineSegmenti B(Pointi(x1,y1), Pointi(x2,y2) ); Bresenham(image.data(), width, height, B.s.x, B.s.y, B.e.x, B.e.y, Black); //交点があれば着色 Pointi c; if (isCrossPixel(&c, A, B) == true) { image[c.y*width + c.x] = Blue; } } //ファイル保存 char fname[100]; sprintf(fname, R"(C:\test\ret.ppm)", 10); ppmP3_write(fname, width, height, image.data()->data(), 255); }
//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255 //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax ) { FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2] ); k++; } fprintf(fp, "\n"); } fclose(fp); }
そのまま、ブレゼンハムのC++での実装。
元のコードはWikipediaから:
//! @brief ブレゼンハムの直線描画 //! @param [out] img 画像データ(一次元配列)へのポインタ //! @param [in] width 画像の幅(画素数) //! @param [in] height 画像の高さ(画素数) //! @param [in] sx 線の始点X //! @param [in] sy 線の始点Y //! @param [in] ex 線の終点X //! @param [in] ey 線の終点Y //! @param [in] color 線の色 template<typename PixelType> void Bresenham( PixelType* img, const int width, const int height, const int sx, const int sy, const int ex, const int ey, const PixelType color ) { // https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%AC%E3%82%BC%E3%83%B3%E3%83%8F%E3%83%A0%E3%81%AE%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0 const int dx = std::abs(ex - sx); const int dy = std::abs(ey - sy); const int nx = (sx < ex) ? 1 : -1; const int ny = (sy < ey) ? 1 : -1; int err = dx - dy; int x = sx; int y = sy; while (1) { if (x >= 0 && y >= 0 && x < width && y < height) { img[y*width + x] = color; } if (x == ex && y == ey) break; const int e2 = 2 * err; if (e2 > -dy) { err = err - dy; x += nx; } if (e2 < dx) { err = err + dx; y += ny; } } }
データは前に作ったNByteDataを使用して、3Byteで1ピクセルとする。
https://www.study.suzulang.com/2dcg-functions/nbyte-data-type
#pragma warning(disable:4996) #include <iostream> #include <vector> #include"NByteData.hpp" #include"Bresenham.hpp" void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax );
int main() { using ucrgb = NByteData<3>; // RGB 計3Byteのデータ型 int width = 200; int height = 200; std::vector<ucrgb> image; ucrgb Red = ucrgb{ 255,0,0 }; ucrgb White = ucrgb{ 255,255,255 }; image.resize(width*height, White);
// 直線描画 Bresenham(image.data(), width, height, -23, 24, 150, 173, Red);
// 結果をファイルへ保存 ppmP3_write(R"(C:\test\a.ppm)",width,height,image.data()->data(),255); }
//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255 //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax ) { FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2] ); k++; } fprintf(fp, "\n"); } fclose(fp); }
2. 以下からHDRIをダウンロードし、Worldに設定して山の背景が空になるように回転する。(元の動画ではカメラ位置が任意なので下記の通りに設定してもあまり意味がない)。
https://hdrihaven.com/hdri/?h=sunflowers
3. テクスチャをダウンロードする(TexturesCom_Nature_Moss)
https://www.textures.com/download/PBR0426/137222
LandscapeにMaterialを設定する。albedo,roughness,height,normalの各テクスチャを以下のように設定する
https://www.transparentpng.com/details/texture-hackberry-tree-_6385.html
https://www.transparentpng.com/details/texture-hackberry-tree-_6386.html
普通にPlaneに貼り付けても勿論いいが、Import Images as Planesアドオンを有効にしておくと楽ができる。
2. Editモードで木のPlaneを複製、回転し、4枚のPlaneが交差した形状にする。さらにOriginを根元に合わせる。
3. 6385,6386の画像で木を作ったら、それらをCollectionに入れる。Collection名はtreesとしておく。
4. カメラの範囲内の頂点のみを選択し、Vertex Groupを作成する。
5. Hairを追加し、木を生やす設定をする。
多分ライトの当て方が悪くて今一つ格好よくない。
チュートリアル自体はもうちょっとだけ続いているが霧をかけるだけなのでやめておく。
初めて知った。可変長テンプレート引数を使ったからと言って必ずしも再帰せずにすむ。
template<typename... Args> void myprint(const Args... args) { //型が違うと暗黙キャストがきなくてエラーになったりするのでstatic_castする auto ini_list = { static_cast<double>(args)... };// init_listの型はinitializer_list<double> for (const auto d : ini_list) { std::cout << d << std::endl; } }
int main() { myprint(3.5, 3, 5.f); }
上記の用途なら以下のように書くとシンプルになる。
#include <string> template<typename... Args> void myprint(const Args... args) { for (const auto& s: { std::to_string(args)... }) { std::cout << s << std::endl; } }
Blenderの有料アドオンAsset Management。有料であるが故に日本語の使い方を書いているところが少ないのでさらっと書いておきたい。
使うまでのステップは
1.アセットを保存するフォルダをExplorerなどでPC上に作成(以下、「アセットデータ保存先」とする)
2.そのアセットデータ保存先に保存したいアセットの種類(モデル,マテリアル,...)を指定
3.アセットの整理に使うカテゴリを登録。アセットをディレクトリツリーのように管理できる
保存先をExplorerなどでPC上に作成し、そのパスを指定する。このパスは複数登録して切り替えできるので、「仕事用」「趣味用」などのように分けて管理してもいい。
・assets(モデル+マテリアル)
・materials(マテリアル)
・HDRI (環境テクスチャ)
・scenes(シーン丸ごと一個)
から選ぶ。
下の図では、「assets」という大カテゴリの中に中カテゴリNatureを作り、その下に小カテゴリtreesを作成している。
オブジェクトを選択しアセット追加ボタン(+)を押すと、下の図ではtreesカテゴリに選択中のオブジェクトを保存できる。
この時の設定項目は
EXPORT MESH ASSET ... 選択したものをどのように保存するか。大体全部チェックしとけばいい
Render Type ... サムネイルの作り方。
use object's material ... サムネイルの作り方。
Format ... サムネイルの作り方
Pack textures ... テクスチャをモデルと一緒にまとめて保存するか。
しかし酷いコードだ。余りに酷すぎるから投稿するか悩んだほどだ。たまにこれやるんだがFloodFillでやる必要はない気がする。
でももっと普通のコードを書くのが間に合わなかったので仕方が無いから出す。
関数呼び出しでは何が行われているか。ローカル変数をスタックに積んでループしている。であれば、ローカル変数をヒープに積めばStack over flowにはならない。だからローカル変数を構造体にまとめ、その構造体をヒープ上のスタック構造で管理する。さらに関数呼び出しは行わず、代わりにGoto文で制御する。
#pragma once #include "NByteData.hpp" //////////////////////////////////////////// //////// using uc3T = NByteData<3>; //! @brief 座標計算などを行う補助クラス class accessor { int width; int height; public: void set(int Width, int Height) { width = Width; height = Height; } bool is_in(const int x, const int y) { if (x < 0 || y < 0 || x >= width || y >= height) return false; return true; } size_t get_pos(const int x, const int y) { return (size_t)y * width + (size_t)x; } }; /////////////////////////////////////// // ff_fillのローカル変数とreturnしたときに戻る先 // 本当は全ての引数を保存するのが正しいが、関数呼び出しで変化しない変数は // 保存する必要がないので省いている struct StackItem { int x; int y; uc3T* node; int return_to_where; }; // 関数呼び出し時のスタックの挙動を再現する struct MyStack { std::vector< StackItem > stack; void push(StackItem data) { stack.push_back(data); } bool isEmpty() { return stack.size() != 0; } StackItem pop() { StackItem value = stack.back(); stack.pop_back(); return value; } }; ///////////////////////////////////////
//! @brief floodfill本体 //! @param [in] x 対象の画素のX座標 //! @param [in] y 対象の画素のY座標 //! @param [in] targetcolor 塗りつぶし対象の色 //! @param [in] replacementcolor 塗りつぶし結果の色 //! @param [in,out] img 対象の画像データ //! @param [in] acc 画素の座標等を求めたりする補助クラスのインスタンス void ff_fill( int _x, int _y, uc3T targetcolor, uc3T replacementcolor, uc3T* img, accessor* acc) { MyStack ms; StackItem items; //この関数のローカル変数 items.x = _x; items.y = _y; items.return_to_where = 4; Entry:; if (acc->is_in(items.x, items.y) == false) { //return switch (items.return_to_where) { case 0:goto Return_0; case 1:goto Return_1; case 2:goto Return_2; case 3:goto Return_3; case 4:goto Return_4; default:throw "call error"; } } items.node = &img[acc->get_pos(items.x, items.y)]; //1. If target-color is equal to replacement-color, return. if (targetcolor == replacementcolor) { //return switch (items.return_to_where) { case 0:goto Return_0; case 1:goto Return_1; case 2:goto Return_2; case 3:goto Return_3; case 4:goto Return_4; default:throw "call error"; } } //2. If the color of node is not equal to target-color, return. if (*items.node != targetcolor) { //return switch (items.return_to_where) { case 0:goto Return_0; case 1:goto Return_1; case 2:goto Return_2; case 3:goto Return_3; case 4:goto Return_4; default:throw "call error"; } } // 3. Set the color of node to replacement-color. *items.node = replacementcolor; ///////////////////// // Perform Flood-fill(one step to the south of node, target-color, replacement-color). ms.push(items); items.x = items.x; items.y = items.y + 1; items.return_to_where = 0; goto Entry;//ff_fill(items.x, items.y + 1, targetcolor, replacementcolor, img, acc); Return_0:; items = ms.pop(); ///////////////////// ///////////////////// // Perform Flood-fill(one step to the north of node, target-color, replacement-color). ms.push(items); items.x = items.x; items.y = items.y - 1; items.return_to_where = 1; goto Entry;//ff_fill(items.x, items.y - 1, targetcolor, replacementcolor, img, acc); Return_1:; items = ms.pop(); ///////////////////// ///////////////////// // Perform Flood-fill(one step to the west of node, target-color, replacement-color). ms.push(items); items.x = items.x-1; items.y = items.y; items.return_to_where = 2; goto Entry;//ff_fill(items.x - 1, items.y, targetcolor, replacementcolor, img, acc); Return_2:; items = ms.pop(); ///////////////////// ///////////////////// // Perform Flood-fill(one step to the east of node, target-color, replacement-color). ms.push(items); items.x = items.x+1; items.y = items.y; items.return_to_where = 3; goto Entry;//ff_fill(items.x + 1, items.y, targetcolor, replacementcolor, img, acc); Return_3:; items = ms.pop(); ///////////////////// Return_4:; // 5. Return. //return switch (items.return_to_where) { case 0:goto Return_0; case 1:goto Return_1; case 2:goto Return_2; case 3:goto Return_3; case 4:return;//goto Return_4; default:throw "call error"; } }
//! @brief floodfillエントリポイント //! @param [in] seedx 塗りつぶし開始点 //! @param [in] seedy 塗りつぶし開始点 //! @param [in] replacementcolor 塗りつぶし結果の色 //! @param [in,out] img 対象の画像データ //! @param [in] Width 画像幅(画素数) //! @param [in] Height 画像高さ(画素数) void f_fill( int seedx, int seedy, uc3T replacementcolor, uc3T* img, int Width, int Height) { accessor acc; acc.set(Width, Height); if (acc.is_in(seedx, seedy) == false) return; uc3T targetcolor = img[acc.get_pos(seedx, seedy)]; ff_fill(seedx, seedy, targetcolor, replacementcolor, img, &acc); } //////// ////////////////////////////////////////////
Flood fillを実装する。データには前回作ったNByteDataのクラスを使う。
WikipediaのFloodFillの項目に疑似コードがあるので、それをC++に書き直す。
https://en.wikipedia.org/wiki/Flood_fill
#pragma once #include "NByteData.hpp" using uc3T = NByteData<3>;
//! @brief 座標計算などを行う補助クラス class accessor { int width; int height; public: void set(int Width, int Height) { width = Width; height = Height; } bool is_in(const int x, const int y) { if (x < 0 || y < 0 || x >= width || y >= height) return false; return true; } size_t get_pos(const int x, const int y) { return (size_t)y * width + (size_t)x; } };
//! @brief floodfill本体 //! @param [in] x 対象の画素のX座標 //! @param [in] y 対象の画素のY座標 //! @param [in] targetcolor 塗りつぶし対象の色 //! @param [in] replacementcolor 塗りつぶし結果の色 //! @param [in,out] img 対象の画像データ //! @param [in] acc 画素の座標等を求めたりする補助クラスのインスタンス void ff_fill( int x, int y, uc3T targetcolor, uc3T replacementcolor, uc3T* img, accessor* acc) { if (acc->is_in(x, y) == false) return; uc3T* node = &img[acc->get_pos(x, y)]; //1. If target - color is equal to replacement - color, return. if (targetcolor == replacementcolor) { return; } //2. If the color of node is not equal to target - color, return. if (*node != targetcolor) { return; } // 3. Set the color of node to replacement - color. *node = replacementcolor; // 4. ff_fill(x, y + 1, targetcolor, replacementcolor, img, acc); // Perform Flood - fill(one step to the south of node, target-color, replacement-color). ff_fill(x, y - 1, targetcolor, replacementcolor, img, acc); // Perform Flood - fill(one step to the north of node, target-color, replacement-color). ff_fill(x - 1, y, targetcolor, replacementcolor, img, acc); // Perform Flood - fill(one step to the west of node, target-color, replacement-color). ff_fill(x + 1, y, targetcolor, replacementcolor, img, acc); // Perform Flood - fill(one step to the east of node, target-color, replacement-color). // 5. Return. return; }
//! @brief floodfillエントリポイント //! @param [in] seedx 塗りつぶし開始点 //! @param [in] seedy 塗りつぶし開始点 //! @param [in] replacementcolor 塗りつぶし結果の色 //! @param [in,out] img 対象の画像データ //! @param [in] Width 画像幅(画素数) //! @param [in] Height 画像高さ(画素数) void f_fill( int seedx, int seedy, uc3T replacementcolor, uc3T* img, int Width, int Height) { accessor acc; acc.set(Width, Height); if (acc.is_in(seedx, seedy) == false) return; uc3T targetcolor = img[acc.get_pos(seedx, seedy)]; ff_fill(seedx, seedy, targetcolor, replacementcolor, img, &acc); }
#include <iostream> #include<vector> #pragma warning(disable:4996) #include "NByteData.hpp" #include "floodfill.hpp" void pnmP3_Write( const char* const fname, const int vmax, const int width, const int height, const unsigned char* const p); bool pnmP6_Read( const char* fname, int* vmax, int *width, int *height, unsigned char** p); // PPM BINARY uc3T getrgb( unsigned char r, unsigned char g, unsigned char b ) { return uc3T({ r,g,b }); }
int main() { int width = 255; int height = 30; unsigned char* image; int vmax; pnmP6_Read(// 画像準備 R"(C:\data\p6ascii.ppm)", &vmax, &width, &height, &image ); uc3T* img = (uc3T*)image; f_fill(// 塗りつぶし 300, 200, getrgb(0, 0, 0), img, width, height );
// 結果をファイルに出力 pnmP3_Write(R"(C:\data\floodfill.ppm)", 255, width, height, image); }
///////////////////////////////////////////////////////////////// //! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] vmax 全てのRGBの中の最大値 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void pnmP3_Write( const char* const fname, const int vmax, const int width, const int height, const unsigned char* const p) { // PPM ASCII FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2]); k++; } fprintf(fp, "\n"); } fclose(fp); }
//! @brief PPM(RGB各1byte,カラー,バイナリ)を読み込む //! @param [in] fname ファイル名 //! @param [out] vmax 全てのRGBの中の最大値 //! @param [out] width 画像の幅 //! @param [out] height 画像の高さ //! @param [in] 画像を読み込んだメモリアドレスを返すためのポインタへのポインタ //! @retval true 成功 //! @retval false 失敗 //! @warning RGBが各1byteでないと動作しない //! @details ファイルを読み込み、width*height*3のメモリを確保したうえでRGBをそこへ格納する bool pnmP6_Read(const char* fname, int* vmax, int *width, int *height, unsigned char** p) { // PPM BINARY *width = -1; *height = -1; *vmax = -1; FILE* fp; fp = fopen(fname, "rb"); char tmp[2048]; char c; while (c = fgetc(fp)) { if (isspace(c)) continue; if (c == 'P') { //フォーマットを特定する c = fgetc(fp) - '0'; if (c != 6) { fclose(fp); return false; } continue; } if (c == '#') { //コメントを読み飛ばす while (c != '\r' && c != '\n') c = fgetc(fp); continue; } if (*width < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *width = atoi(tmp); break; } } continue; } if (*height < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *height = atoi(tmp); break; } } continue; } if (*vmax < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *vmax = atoi(tmp); break; } } break; } else { break; } } if (*width < 0 || *height < 0 || *vmax < 0) { return false; } const size_t maxsize = *width* *height; unsigned char r, g, b; *p = new unsigned char[maxsize * 3]; for (size_t i = 0; i < maxsize; i++) { fread(&r, 1, 1, fp); fread(&g, 1, 1, fp); fread(&b, 1, 1, fp); (*p)[i * 3 + 0] = r; (*p)[i * 3 + 1] = g; (*p)[i * 3 + 2] = b; } fclose(fp); return true; }
FloodFillは再帰を使うが、再帰はスタックを食いつぶすのでStack over flowが起こる。
VC++の初期設定は1MBらしいので、[プロジェクトのプロパティ]→[リンカー]→[システム]→[スタックのサイズ設定]からスタックを大きめに確保しておく。なおここはバイト単位なのでメガバイト単位で指定するには1024*1024しなければならない。
Gantry 5はJoomlaの、かなり柔軟なテンプレートだと思えばいい。ただし、モジュール本体+テンプレートデータ という構成になっているので、まずモジュールをインストールし、次に(好みの)テンプレートをインストールし、そしてテンプレートをカスタマイズするという作業が必要になる。
joomla-pkg_gantry5_**** ... 本体
joomla-tpl_**** ... テンプレートデータ
pkg,tpl両方のzipファイルをエクステンションからインストールする。
Outlineを複製して新たなOutlineを作る。Outlineは縦三分割とかのページの基本構造。
画面右上の「Outline」へ行き、新たなアウトラインを作成する。
左側にメニューを配置するとか配色を変えるとかする。
なおメニュー項目の順番などは右上の「Menu」からできる。
なんとなくいじっていればできる。
なぜかメニュー項目が横並びになってしまったり、preタグの背景色が親オブジェクトから継承されていなかったりと、コピペやマウス操作だけではどうにもならないところがあったので、CSSを一部書き足す方法をメモしておく。
ブロックはdivで囲まれているので、歯車アイコンをクリックしてそこに付加するclassやidを設定できる。
CSSを追記する。今回メニューが勝手に横並びになっていたので、#clearfloatの入れ子になっているliの設定を以下のように記述。
下記のように、パラメータパックで入ってきたクラス全部が特定のクラスの子であることをチェックしたい。探したが見つからなかったので自分で作る。
// OK template<class Base,class Class> class MyClass { static_assert(std::is_base_of<Base, Class>::value, " Class is invalid"); }; // NG template<class Base, class... Classes> class MyClass2 { static_assert(std::is_base_of<Base, Classes...>::value, " Class are invalid"); };
まず、以下のようにテスト用のクラスを定義しておく。
class MyBase { int a; }; class MyDeri1 :public MyBase{ int b; }; class MyDeri2 :public MyBase { int c; }; class Independent{ int d; };
次に、そもそもstd::is_base_ofのvalueが型としては何なのかを知りたいので、以下のようにチェックする。
std::cout << std::is_base_of< std::true_type, std::is_base_of<MyBase, MyDeri1>>::value;
出力:
と、std::is_base_ofは最終的にはstd::true_typeかstd::false_typeになることが分かったので、それと同じようになるようにare_derived_fromも実装する。are_derived_fromが英語的にどうかは知らい。
template<class Base, class CHead, class ...Classes> struct are_derived_from : public std::conditional< std::is_base_of<Base, CHead>::value, //CHeadに対して評価 are_derived_from<Base, Classes...>, // 上評価がtrueならそれ以外の型についても調べる std::false_type // 一つでもfalseなら全部falseになる >::type { }; //末尾の特殊化 template<class Base, class CTail> struct are_derived_from<Base, CTail> : public std::is_base_of<Base, CTail> { };
int main() { std::cout << are_derived_from<MyBase, MyDeri1, MyDeri2>::value << std::endl; std::cout << are_derived_from<MyBase, MyDeri1, Independent>::value << std::endl; }
出力:
1
0
JoomlaのURLはデフォルトではとても複雑で、SEO Friendly URLの設定をすることでだいぶすっきりするが、それでもindex.phpや数字が残る。これを取り除く。
システム→グローバル設定
グローバル設定 → サイト
→ フレンドリURL ... はい
→ URLリライトの使用 ... はい
URLリライトのために、サーバーに置いてある「htaccess.txt」のファイル名を「.htaccess」に変更
※もしInternal Server Error が出るなら以下を参照
Joomla!備忘録
.htaccess にファイル名を変更した内容を以下の部分を書き加えて下さい。
14行目(Joomla!3.4.1 オリジナルの場合)
Options +FollowSymlinks
の先頭に(#)を追加して保存して下さい。
https://joomla.mdeps.com/nmerror/urlrwerr.html
記事→ 統合
→ URLルーティング ... モダン
→ URLからIDを削除する ... はい
https://www.joomshaper.com/blog/making-joomla-site-seo-friendly-by-removing-id-from-urls
https://www.joomlabeginner.com/blog/tutorials/116-how-to-remove-ids-and-numbers-from-joomla-urls