2018年9月24日 星期一

初探Chromium Embedded Framework

初探Chromium Embedded Framework

前言

  Chromium Embedded Framework以下簡稱為"cef",是一個可以在Native App裡使用Chrome的Framework,知名的"Electron"的前端就是"cef",由於是個開源專案,所以來研究一下在此做個紀錄。

內容

  如果上網搜尋很容易會找到開源專案的位置 Chromium Embedded Framework Overview,開啟後會得到Git的URL,如下圖
cef的Git
如果就這麼開開心心的用Git直接Clone的話,就等於在"浪費時間",因為那個Git並不是用來給使用者存取的,而是給"cef"的自動建置流程來存取的!所以下載完後會發現根本不能建置。那麼該如何建置"cef",請參考MasterBuildQuickStart

  在開啟MasterBuildQuickStart後,先看"File Structure",如下圖
cef的File structure
這個"File Structure"有一部分是要手動建構的,不是自動產生的,該網頁還會說明各個平台建置需求與步驟,以下說明為Windows平台。雖說網頁上說要準備40G的空間,但我實際建置64位元Debug版後,發現大概要55G,建立前請確認空間是否充足。

  第一步是建立"automate"與"chromimu_git"資料夾,說明的Root資料夾為"c:\code",請示需求更改Root資料夾。

  第二步是在Root資料夾下建立"depot_tools"資料夾,並下載說明中的"depot_tools.zip",如下圖
下載"depot_tools.zip"
載完後,解壓到"depot_tools"資料夾下即可。

  第三步是執行"update_depot_tools.bat",這個檔案在"depot_tools"資料夾裡,但在執行前請確認Python的版本,限定2.7.x版才可以成功建置,如果是3以後的版本請切換。

  第四步是將"depot_tools"資料夾的位址加到環境變數"path"裡。

  第五步是下載"automate-git.py",如下圖
下載"automate-git.py"
下載後放到"automate"資料夾下。

  第6步是製造"update.bat",到"chromimu_git"的資料夾下新增一個"update.bat",內容如下
set GN_DEFINES=use_jumbo_build=true
set GN_ARGUMENTS=--ide=vs2017 --sln=cef --filters=//cef/*
python ..\automate\automate-git.py --download-dir=c:\code\chromium_git --depot-tools-dir=c:\code\depot_tools --no-distrib --no-build

注意一下紅色的內容,有兩個檔案位置會因為Root資料夾的位置不一樣而改變,請示需要修改。改完後直接執行"update.bat"。這個步驟有點就大概要一個半小時,執行中如果中斷就要重來,請先注意。

  第7步是製造"create.bat",到"(root)\chromimu_git\chromium\src\cef"下,製造"create.bat",內容如下
set GN_DEFINES=use_jumbo_build=true
set GN_ARGUMENTS=--ide=vs2017 --sln=cef --filters=//cef/*
call cef_create_projects.bat

改完後就直接執行。

  第8步就是啟動自動建置,到"(root)\chromimu_git\chromium\src",執行以下命令
ninja -C out\Debug_GN_x64 cef

這種個建置過程非常久,在我的電腦是3個小時,請注意。如果建置完成就可以在"(root)\chromimu_git\chromium\src\out\Debug_GN_x64"下找到"cefclient.exe",執行後就可得到一個cef的視窗。


參考資料

MasterBuildQuickStart

2018年9月17日 星期一

DirectInput的學習心得(2)

DirectInput的學習心得(2)

前言

  續前篇DirectInput的學習心得(1),本次紀錄用DirectInput來偵測手把(Gamepad)。為何不直接使用XInuput呢?為了相容一些較舊的手把,就這麼簡單。如果同時使用DirectInput與XInuput時會有一些要注意的,就是DirectInput可以抓到XInuput的手把,本篇會記錄如何濾除,在此做個紀錄。

內容

  在開始偵測手把時,要先製造出IDirectInput8,如果不知道如何製造,請參照前篇DirectInput的學習心得(1),接著跟上次鍵盤的狀況不太一樣,由於系統鍵盤只會有一個,所以不需要去列舉裝置,但手把可能不只一個,所以透過EnumDevices()來抓出有多少個手把,接著對每個手把做SetCooperativeLevel()與SetDataFormat(),這部分和上次的鍵盤一樣,但這次要多一個步驟,就是透過EnumObjects()來取得類比按鈕的資訊,最後完成後一樣要Acqurie()。
  來看看範例程式,如下
#include "dinput.h"
#include "dinputd.h"
struct SEnumJoystickContext
{
  IDirectInput8* pDirectInput;
  HWND hWnd;
  std::vector<IDirectInputDevice8>& deviceList;
};
BOOL CALLBACK EnumJoystickCallBack(const DIDEVICEINSTANCE* pdidInstance,VOID* pContext)
{
  //Cast context...
  SEnumJoystickContext* pEnumJoystickContext=(SEnumJoystickContext*)pContext;

  //
  LPDIRECTINPUTDEVICE8 pNewDevice=NULL;
  HRESULT hr=pEnumJoystickContext->pDirectInput->CreateDevice(pdidInstance->guidProduct,&pNewDevice,NULL);
  if(hr != DI_OK)
    return DIENUM_STOP;
  //
  pEnumJoystickContext->deviceList.push_back(pNewDevice);
  return DIENUM_CONTINUE;
}
BOOL CALLBACK EnumObjectsCallBack(const DIDEVICEOBJECTINSTANCE* pdidoi,VOID* pContext)
{
  IDirectInput8* pDevice=(IDirectInput8*)pContext;
  if(pdidoi->dwType &DIDFT_AXIS)
  {
    DIPROPRANGE diprg;
    diprg.diph.dwSize=sizeof(DIPROPRANGE);
    diprg.diph.dwHeaderSize=sizeof(DIPROPHEADER);
    diprg.diph.dwHow=DIPH_BYID;
    diprg.diph.dwObj=pdidoi->dwType;
    diprg.lMin=-1000;
    diprg.lMax=1000;
    //
    if(pDevice->SetProperty(DIPROP_RANGE,&diprg.diph)!=DI_OK)
      return DIENUM_STOP;
  }
  return DIENUM_CONTINUE;
}
void DetectedGamepad(IDirectInputDevice8* pDIDeviceGamepad)
{
  DIJOYSTATE2 gamepadState;
  pDIDeviceGamepad->GetDeviceState(sizeof(DIJOYSTATE2), &gamepadState);
  //Get x axis was left
  if(gamepadState.lX < 0)
  {
    //do something...
  }
  //Get button 0 state
  if(gamepadState.rgbButtons[0] &0x80)
  {
   //do something...
  }
}
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
  //Init main window and IDirectInput8
  //...
  HWND mainWin = initMainWindow;
  IDirectInput8* pDirectInput = initDirectInput;
  std::vector<IDirectinputdevice8*> deviceList;
  //
  SEnumJoystickContext enumJoystickContext = { pDirectInput, mainWin, deviceList};
  pDirectInput-&gt;EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoystickCallBack, &enumJoystickContext, DIEDFL_ATTACHEDONLY) 
  for(int i=0;i<deviceList.size();i++)
    pGamepadDevice->SetCooperativeLevel(mainWin,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    pGamepadDevice->SetDataFormat(&c_dfDIJoystick2);
    //
    pGamepadDevice->EnumObjects(EnumObjectsCallBack, pGamepadDevice, DIDFT_ALL);
    pGamepadDevice->Acquire();
  }
  //Init gamepad end...
}

在製造完IDirectInput8後,會看到一個SEnumJoystickContext的Struct與 EnumDevices(),SEnumJoystickContext是要傳給EnumDevices()的參數,再看看EnumDevices()的參數會發現一個CallBack function名為EnumJoystickCallBack,這個CallBack 會在每次被IDirectInput8列許到裝置後被喚起,看看喚起的EnumJoystickCallBack(),有個引數名為"pContext",會發現在開頭就被轉型為SEnumJoystickContext,也會看到另一個引數"pdidInstance",這次必須透過DIDEVICEINSTANCE來製造出IDirectInputDevice8,這個CallBack 的回傳值有"DIENUM_CONTINUE"與"DIENUM_STOP","DIENUM_CONTINUE"表示持持續列舉,"DIENUM_STOP"則代表中止列舉,在EnumDevices()結束後,所有的IDirectInputDevice8都會在"deviceList",接著就初始化每個裝置。初始化裝置和鍵盤很像,一樣是SetCooperativeLevel()與SetDataFormat(),但這次要多一個步驟,就是EnumObjects(),EnumObjects()一樣需要一個CallBack ,這個CallBack 主要用來初始化類比按鍵,最後一樣要Acquire()。偵測的按鍵的部分可以參考範例的DetectedGamepad(),透過GetDeviceState()可以得到"DIJOYSTATE2",裡面就會放置所有按鍵的值,詳細地的說明可以到DIJOYSTATE2 Structure查詢。
  如果需要與XInuput同時使用,官方的提供的做法是過濾掉XInuput的裝置,官方提供一個檢查是否為XInuput裝置的function,這個function可以用在EnumJoystickCallBack()裡去做濾除的動作,詳細的程式碼可以在XInput and DirectInput裡找到。

參考資料

XInput and DirectInput
DIJOYSTATE2 Structure

相關文章

DirectInput的學習心得(1)

2018年9月10日 星期一

DirectInput的學習心得(1)

DirectInput的使用(1)

前言

  在偵測輸入時可以採用XInput來偵測,可以參考XInput的使用這一篇,但XInput並不支援鍵盤與滑鼠,且手把要是年代較久遠的話也會無法被XInput偵測到。為什麼不用訊息幫浦來偵測輸入呢?理由很簡單,作業系統的訊息幫浦沒辦法調整偵測的速度,像現代電玩時常需求的FPS60偵測速度是達不到的,就我以前實測的經驗,訊息幫浦不但偵測速度不夠,而且偶爾還有掉輸入(明明有輸入卻沒反應),雖然不知是否為偵測速度不夠造成,但用DirectInput偵測鍵盤的輸入,這個狀況就可以得到解決。這次的紀錄只會記錄到鍵盤的偵測與使用,在此做個紀錄。

內容

  使用DirectInput的過程並不會太繁雜,首先使用DirectInputCreate()製造出IDirectInput8,這個介面只需造一次即可,接著用IDirectInput8來製造出IDirectInputDevice8,而依據製造的參數不同可以讓IDirectInputDevice8代表鍵盤、滑鼠與手把,本篇將說明如何將IDirectInputDevice8初始化為鍵盤。
  初始化鍵盤的範例碼如下

#include <dinput>
#include <dinputd>
void DetectedKeyboardKeys(IDirectInputDevice8* pDIDeviceKeyboard)
{
  unsigned char keyState[256];
  if(pDIDeviceKeyboard->etDeviceState(256,keyState) != DI_OK)
    return;
  //if GetDeviceState() is fined,keyState will save all key state from keyboard...
  //Follow detect sample...
  //Is num 0 pressed? 
  if(keyState[DIK_0])
  {
    //do something...
  }
  //Is key a pressed? 
  if(keyState[DIK_A])
  {
    //do something...
  }
  
}
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
  //Init main window
  //...
  HWND mainWin = initMainWindow;
  //Init DiectInput...
  IDirectInput8* pDirectInput = nullptr;
  if(DirectInput8Create(hInstance,DIRECTINPUT_VERSION,IID_IDirectInput8,(void**)&pDirectInput ,NULL) != DI_OK)
    return 1;
  //Init keyboard
  IDirectInputDevice8* pDIDeviceKeyboard = nullptr;
  if(pDirectInput->CreateDevice(GUID_SysKeyboard,&pDIDeviceKeyboard ,NULL) != DI_OK)
  {
    pDirectInput->Release(); 
    return 1;
  }
  //
  if(pDIDeviceKeyboard->SetCooperativeLevel(mainWin,DISCL_FOREGROUND|DISCL_NONEXCLUSIVE) != DI_OK)
  {
    pDIDeviceKeyboard->Release();
    pDirectInput->Release(); 
    return 1;
  }
  if(pDIDeviceKeyboard->SetDataFormat((DIDATAFORMAT*)&amp;c_dfDIKeyboard) != DI_OK)
  {
    pDIDeviceKeyboard->Release();
    pDirectInput->Release(); 
    return 1;
  }
  //
  pDIDeviceKeyboard->Acquire();
  //Init keyboard end
  // 
  return 0;
}

在主程式一如之前所說製造出IDirectInput8,要注意HINSTANCE是必要的參數,再來是透過 IDirectInput8的CreateDevice()來製造出IDirectInputDevice8,並可載參數上看到"GUID_SysKeyboard",這個參數會決定裝置是鍵盤、滑鼠或手把,接著是SetCooperativeLevel(),主要的目的確定要偵測哪個視窗的輸入,SetDataFormat()決定偵測資料的輸出格式,在鍵盤時這是固定的,所以用c_dfDIKeyboard來設定即可,接著Acquire(),這個過程很重要,初始化的最後一個流程,勢必要流程請勿省略,Acquire()如果失敗表示該裝置目前沒連上,以上就是初始化鍵盤的部分。偵測的部分在DetectedKeyboardKeys(),首先準備好一塊Buffer,範例使用的是keyState,256是鍵盤上所有的鍵的大小,透過GetDeviceState()來取得所有按鍵的輸入狀態,取完之後如何取得要偵測的按鍵的狀態呢?可以參考
Keyboard Device Enumeration,用該列舉來做為索引值取得目前按鍵的狀態,如果不是0就代表按下。

參考資料

Capturing DirectX DirectInput Keyboard and Mouse Input
DirectInput
Keyboard Device Enumeration

相關文章

DirectInput的學習心得(2)

2018年9月3日 星期一

從Blender裡萃取模組資料(8)

從Blender裡萃取模組資料(8)

前言

  續前篇從Blender裡萃取模組資料(7),這次說明萃取Skeleton的動畫,在此做個紀錄。

內容

  如果使用前篇的範例來萃取Skeleton的動畫,應該會發現竟然完全沒問題,可以正常執行,但若仔細一看"Data path"的欄位,就會發現它有何不同了,就拿"location"來說,在Skeleton的動畫可能會是"pose.bones["spine"].location",其中的"spine"為該Bone的名稱。這裡有個陷阱!如果你直覺地用字串的split()來parse這個"Data path"的欄位的話會有問題,問題會發生在Bone的名稱是可以包含".",舉例來說如果Bone的名稱是"spine.001"的話,"Data path"就會變成"pose.bones["spine.001"].location",接著split()後就變成"[pose, bones["spine, 001, location]",明顯的,Bone的名稱會被切到,還必須注意Blender的名稱可以很自由,名稱裡可以出現單引號或雙引號,所以需要特製的Parse。

  接著用前篇的範例為基礎,製作出以下範例
import bpy

def ShowActionInfo(act):
  print('animation name',act.name)
  for curve in act.fcurves:
    print('Data path:',curve.data_path,' ',end='')
    print('')
    if curve.data_path == 'location':
        print("Data from transform.location")
    elif curve.data_path == 'rotation_euler':
        print("Data from transform.rotation_euler")
    elif curve.data_path == 'rotation_euler':
        print("Data from transform.rotation_quaternion")
    elif curve.data_path == 'scale':
        print("Data from transform.scale")
    elif curve.data_path[:12] == 'pose.bones["':
        namelastIndex = curve.data_path.rfind('"')
        if namelastIndex >= 0:
            boneName = curve.data_path[12:namelastIndex]
            propertyNameIndex = curve.data_path.rfind('.')
            if propertyNameIndex >= 0:
                propertyName = curve.data_path[ (propertyNameIndex+1):]
                if propertyName == 'location':
                    print('Bone:',boneName,' Property:transform.location')
                elif propertyName == 'rotation_euler':
                    print('Bone:',boneName,' Property:transform.rotation_euler')
                elif propertyName == 'rotation_quaternion':
                    print('Bone:',boneName,' Property:transform.rotation_quaternion')
                elif propertyName == 'scale':
                    print('Bone:',boneName,' Property:transform.scale')
                else:
                    print('Unknown property:',propertyName)
            else:
                print('Parse skeleton property error!')
        else:
            print('Parse skeleton bone name error!')
    else:
        print("Unknown data path!")
    print('Array index:',curve.array_index,' ',end='')
    print('')
    for keyFrame in curve.keyframe_points:
      print('Key time:',keyFrame.co[0],' ',end='')
      print('Key value:',keyFrame.co[1],' ',end='')
      print('')
#
def ShowAnimationInfo(obj):
  if obj.animation_data == None:
    print("No animation data in object!")
    return
  #Check actived action...
  actionCon = 0
  if obj.animation_data.action != None:
    ShowActionInfo(obj.animation_data.action)
    actionCon+=1
  #Check action in NLA tracks...
  for track in obj.animation_data.nla_tracks:
    for strip in track.strips:
      ShowActionInfo(strip.action)
      actionCon +=1
  print("Animation amount:",actionCon )
#
tagObj = bpy.data.objects['metarig']
ShowAnimationInfo(tagObj )

可以看到ShowActionInfo()裡多了非常多的if判斷,目前沒找到比較簡易的寫法,目前的作法以直觀與好懂為原則, 所以程式碼相當長。Bone的名稱如之前所說由於編輯器可以相當自由命名,所以用rfind()來找,property的部分也 是使用rfind()來找也請注意。

參考資料

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(7)