From 30e15cff4ccc3a0498bde6cc056543b6a7943982 Mon Sep 17 00:00:00 2001 From: Manoj Velmurugan Date: Sun, 9 Apr 2023 09:35:38 +0530 Subject: [PATCH] first commit --- .gitignore | 42 +++ README.md | 123 ++++++- SECURITY.md | 6 + blocks/.gitignore | 0 blocks/dummy.xml | 55 +++ blocks/mjLib.slx | Bin 0 -> 58687 bytes blocks/mj_busExist.m | 4 + blocks/mj_initbus.m | 12 + blocks/mj_maskinit.m | 139 ++++++++ blocks/mj_postcodegen.m | 4 + blocks/mj_postcodegen_grt.m | 18 + blocks/mj_sensor_parser.m | 50 +++ blocks/slblocks.m | 6 + examples/.gitignore | 1 + examples/gettingStarted.slx | Bin 0 -> 42796 bytes examples/monitorTune.slx | Bin 0 -> 37958 bytes install.m | 119 +++++++ license.txt | 11 + src/mj.cpp | 572 +++++++++++++++++++++++++++++++ src/mj.hpp | 194 +++++++++++ src/mj_depth_near_far.cpp | 74 ++++ src/mj_initbus_mex.cpp | 240 +++++++++++++ src/mj_sampletime.cpp | 68 ++++ src/mj_sfun.cpp | 644 +++++++++++++++++++++++++++++++++++ src/semaphore.hpp | 41 +++ src/workspace.code-workspace | 88 +++++ tools/.gitignore | 2 + tools/Makefile | 88 +++++ tools/build.m | 10 + tools/fetchLink.js | 16 + tools/links.json | 40 +++ tools/package.m | 39 +++ tools/setupBuild.m | 56 +++ 33 files changed, 2761 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 SECURITY.md create mode 100644 blocks/.gitignore create mode 100644 blocks/dummy.xml create mode 100644 blocks/mjLib.slx create mode 100644 blocks/mj_busExist.m create mode 100644 blocks/mj_initbus.m create mode 100644 blocks/mj_maskinit.m create mode 100644 blocks/mj_postcodegen.m create mode 100644 blocks/mj_postcodegen_grt.m create mode 100644 blocks/mj_sensor_parser.m create mode 100644 blocks/slblocks.m create mode 100644 examples/.gitignore create mode 100644 examples/gettingStarted.slx create mode 100644 examples/monitorTune.slx create mode 100644 install.m create mode 100644 license.txt create mode 100644 src/mj.cpp create mode 100644 src/mj.hpp create mode 100644 src/mj_depth_near_far.cpp create mode 100644 src/mj_initbus_mex.cpp create mode 100644 src/mj_sampletime.cpp create mode 100644 src/mj_sfun.cpp create mode 100644 src/semaphore.hpp create mode 100644 src/workspace.code-workspace create mode 100644 tools/.gitignore create mode 100644 tools/Makefile create mode 100644 tools/build.m create mode 100644 tools/fetchLink.js create mode 100644 tools/links.json create mode 100644 tools/package.m create mode 100644 tools/setupBuild.m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2953972 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +MJDATA.TXT +MUJOCO_LOG.TXT +*.slxc +*.autosave +*.asv +slprj/ +html/ +*ert_rtw/ +*_ert_shrlib_rtw/ +*_rtw/ +callgrind.out* +*.slx.original +*.zip +*.tar.gz +*.gz +*.o +references/ +*.exe +*.out +*.dylib +*.dll +*.so +*.mexw64 +*.mexa64 +*.mexi64 +*.pdb +BF_RESULT/ +temp/ +model_report.htmx +*.pdb +CP_result/ +BP_result/ +*.r2021b +*.r2022a +*.r2021a +.vs/ +.vscode/ +win64/ +glnxa64/ +maci64/ +maca64/ +arm64/ \ No newline at end of file diff --git a/README.md b/README.md index 31f952f..30157c0 100644 --- a/README.md +++ b/README.md @@ -1 +1,122 @@ -# mujoco-simulink-blockset \ No newline at end of file +# Simulink Blockset for MuJoCo Simulator + +This repository provides a Simulink® C-MEX S-Function block interface to [MuJoCo™ physics engine](https://mujoco.org/). + +[![View on File Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://www.mathworks.com/matlabcentral/fileexchange/####-file-exchange-title) + +Useful for, +1. Robot simulation (mobile, biomimetic, grippers, robotic arm) +2. Development of autonomous algorithms using classical or machine learning based approaches +3. Camera (RGB, Depth) rendering + +## Installation Instructions + +### MathWorks® Products (https://www.mathworks.com) + +- MATLAB® (required) +- Simulink® (required) +- Computer Vision Toolbox™ (optional) +- Robotics System Toolbox™ (optional) +- Control System Toolbox™ (optional) +- Simulink® Coder™ (optional) + +MATLAB R2022b or newer recommended. Install MATLAB with the above products and then proceed to setting up MuJoCo blocks. + +*Note - You may need to rebuild the S-Function if you are using an older release of MATLAB*. + +### Simulink Blockset for MuJoCo + +#### Windows®: + +- Download the latest release of this repository +- Open MATLAB R2022b. In case you are using a older MATLAB release, please follow the build instructions below to rebuild for a particular MATLAB release +- Run install.m in MATLAB command window. MuJoCo and GLFW libraries will be downloaded and added to MATLAB Path. + +#### Ubuntu®: + +- Download the latest release of this repository +- Download and install GLFW library from Ubuntu terminal + + `sudo apt update && sudo apt install libglfw3 libglfw3-dev` +- Open MATLAB R2022b. In case you are using a older MATLAB release, please follow the build instructions below to rebuild for a particular MATLAB release +- Run install.m in MATLAB command window. MuJoCo library will be downloaded and added to MATLAB Path. + +## Usage +Open examples/gettingStarted.slx model and click run + +If the installation is successful, you should see a pendulum model running in a separate window and camera stream displayed by Video Viewer block (Computer Vision Toolbox) + +### Blocks + +![](mjLib.png){height=250px} + +MuJoCo Plant block steps MuJoCo engine, renders visualization window & camera, sets actuator data and outputs sensor readings + +It takes an XML (MJCF) as the main block parameter. It auto detects the inputs, sensors, cameras from XML and sets the ports, sample time and other internal configuration. + +Inputs can either be a Simulink Bus or vector. + +Sensors are output as a Simulink Bus. + +RGB and Depth buffers from camera are output as vectors. These can be decoded to Simulink image/matrix using the RGB and Depth Parser blocks. + +![](getting_started_clip1.mp4 "Block Usage") +![](getting_started_clip2.mp4 "Visualization") + +## Build Instructions (optional) + +Steps for building/rebuilding the C-MEX S-Function code. These instructions are only required if you are cloning the repository instead of downloading the release. + +### Windows: + +- Install one of the following C++ Compiler + - Microsoft® Visual Studio® 2022 or higher (recommended) + - (or) [MinGW (GCC 12.2.0 or higher)](https://community.chocolatey.org/packages/mingw) +- Clone this repository +- Launch MATLAB and open the repository folder + - `>> install` +- Open tools/ + - Open setupBuild.m. In case you are using MinGW compiler, edit the file and set selectedCompilerWin to "MINGW". + - `>> setupBuild` + - `>> mex -setup c++` + - `>> build` + +### Ubuntu + +- Install the tools required for compiling the S-Function + + `$ sudo apt update && sudo apt install build-essential git libglfw3 libglfw3-dev ` +- Clone this repository + + `$ git clone ` +- Launch MATLAB and open the repository folder. Run the install.m script. + - `>> install` +- Open tools/ and run the following commands in MATLAB command Windows + - `>> setupBuild` + - `>> mex -setup c++` + - `>> build` + + +## License + +The license is available in the license.txt file within this repository. + +## Acknowledgements +Cite this work as, + +Manoj Velmurugan. Simulink Blockset for MuJoCo Simulator (https://github.com/mathworks-robotics/mujoco-simulink-blockset), GitHub. Retrieved March 27, 2023. + + +Refer to [MuJoCo repository](https://github.com/deepmind/mujoco) for guidelines on citing MuJoCo physics engine. + +The sample codes and API documentation provided for [MuJoCo](https://mujoco.readthedocs.io/en/latest/overview.html) and [GLFW](https://www.glfw.org/documentation) were used as reference material during development. + +MuJoCo and GLFW libraries are dynamically linked against the S-Function and are required for running this blockset. + +UR5e MJCF XML from [MuJoCo Menagerie](https://github.com/deepmind/mujoco_menagerie/tree/main/universal_robots_ur5e) was used for creating demo videos. + +## Community Support + +You can post your queries on the [MATLAB® Central™ File Exchange](https://www.mathworks.com/matlabcentral/fileexchange/####-file-exchange-title) page. + +Copyright 2023 The MathWorks, Inc. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..221952e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,6 @@ +# Reporting Security Vulnerabilities + +If you believe you have discovered a security vulnerability, please report it to +[security@mathworks.com](mailto:security@mathworks.com). Please see +[MathWorks Vulnerability Disclosure Policy for Security Researchers](https://www.mathworks.com/company/aboutus/policies_statements/vulnerability-disclosure-policy.html) +for additional information. \ No newline at end of file diff --git a/blocks/.gitignore b/blocks/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/blocks/dummy.xml b/blocks/dummy.xml new file mode 100644 index 0000000..9c0cc56 --- /dev/null +++ b/blocks/dummy.xml @@ -0,0 +1,55 @@ + + \ No newline at end of file diff --git a/blocks/mjLib.slx b/blocks/mjLib.slx new file mode 100644 index 0000000000000000000000000000000000000000..c336ebdbeee299e26277b121c154876eaa86d9cf GIT binary patch literal 58687 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 JPDcKC_aEj@Ui*4k&+ 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 P42W*9Bd?bG*SG%%ZFjBJ literal 0 HcmV?d00001 diff --git a/examples/monitorTune.slx b/examples/monitorTune.slx new file mode 100644 index 0000000000000000000000000000000000000000..d5ab49907a18ff054759c60c0eb1678b4fb54182 GIT binary patch literal 37958 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 +mjPath = downloader(urlsList, "mujoco", MJ_VER); +if ~ispc + folder = fullfile(mjPath, ['mujoco-', MJ_VER]); + copyfile(fullfile(folder, '*'), mjPath); + rmdir(folder, 's'); +end + +%% DOWNLOAD GLFW +if ispc + glfwTopPath = downloader(urlsList, "glfw", GLFW_VER); +end + +%% MATLAB PATH ADDITION +addpath(blockPath); +savepath +disp(' ') +disp("MuJoCo block library added to MATLAB path and saved"); + +%% SHARED LIB COPY PATH ADDITION +% Alternatively you can add the dll location to system path +mujocoIncPath = fullfile(mjPath, 'include'); +mujocoImportLibPath = fullfile(mjPath, 'lib'); + +if ispc + mujocoDll = fullfile(mjPath, 'bin', 'mujoco.dll'); + copyfile(mujocoDll, blockPath); + + glfwPath = fullfile(glfwTopPath, ['glfw-', GLFW_VER, '.bin.WIN64']); + glfwIncPath = fullfile(glfwPath, 'include'); + glfwImportLibPath = fullfile(glfwPath, glfwRunTimeLib); + + glfwDll = fullfile(glfwImportLibPath, 'glfw3.dll'); + copyfile(glfwDll, blockPath); +else + mjSo = fullfile(mjPath, 'lib', ['libmujoco.so.', MJ_VER]); + copyfile(mjSo, blockPath); +end + +%% Path (MATLAB) +addpath(srcPath); +addpath(mujocoIncPath); +if ispc + addpath(glfwIncPath); + linkPaths = {glfwImportLibPath, mujocoImportLibPath}; + incPaths = {mujocoIncPath, fullfile(pwd, srcPath), glfwIncPath}; +else + linkPaths = {mujocoImportLibPath}; + incPaths = {mujocoIncPath, fullfile(pwd, srcPath)}; +end +savepath + +%% MEX Build configuration +if ispref('mujoco') + rmpref('mujoco'); +end + +addpref('mujoco', 'MJ_VER', MJ_VER); +addpref('mujoco', 'linkPaths', linkPaths); +addpref('mujoco', 'incPaths', incPaths); +addpref('mujoco', 'srcPaths', {fullfile(pwd, srcPath)}); +if ispc + addpref('mujoco', 'glfwIncPath', glfwIncPath); + addpref('mujoco', 'glfwImportLibPath', glfwImportLibPath); +end + +%% Local functions + function downloadfolder = downloader(urlsList, name, version) + libraryFolder = 'lib'; + downloadfolder = fullfile(pwd, libraryFolder, computer('arch'), name); + urlJson = jsondecode(urlsList); + for i=1:length(urlJson.files) + obj = urlJson.files(i); + if strcmp(obj.name, name) && strcmp(obj.version, version) && strcmp(obj.arch, computer('arch')) + downloadLink = obj.downloadLink; + end + end + disp(' ') + disp('Download URL is:'); + disp(downloadLink); + + if isfolder(downloadfolder) + disp(' ') + disp('folder exists already. it will be overwritten'); + end + + [~,~,ext]=fileparts(downloadLink); + downloadFile = fullfile(libraryFolder, strcat('download', ext)); + status = mkdir(libraryFolder); %#ok + websave(downloadFile, downloadLink); + if strcmp(ext,'.zip') + unzip(downloadFile, downloadfolder); + elseif strcmp(ext, '.gz') + untar(downloadFile, downloadfolder); + else + error('unknown extension. Unable to extract archive'); + end + disp(' ') + disp(name + ' downloaded and extracted to this path:'); + disp(downloadfolder) + end +end diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..f03729e --- /dev/null +++ b/license.txt @@ -0,0 +1,11 @@ +Copyright (c) 2022, The MathWorks, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +3. In all cases, the software is, and all modifications and derivatives of the software shall be, licensed to you solely for use in conjunction with MathWorks products and service offerings. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + diff --git a/src/mj.cpp b/src/mj.cpp new file mode 100644 index 0000000..77882e6 --- /dev/null +++ b/src/mj.cpp @@ -0,0 +1,572 @@ +// Copyright 2022-2023 The MathWorks, Inc. +#include "mj.hpp" +#include +#include +#include +#include + +// STATIC AND GLOBALS + +std::recursive_mutex glfwMutex; +// OpenGL is not reentrant (and not thread safe). +// Lock the graphics library so that no other thread can use opengl. + +using std::shared_ptr; + +// Aligned malloc - Visual Studio Specific implementation +// #include +// #define MALLOC(buf, alignment) _aligned_malloc(buf, alignment) +// #define FREE(buf) _aligned_free(buf) + +#define MALLOC(buf, alignment) malloc(buf) +#define FREE(buf) free(buf) + +// MODEL -------------------------------------------------------------------------- +int MujocoModelInstance::initMdl(std::string file, bool shouldInitCam, bool shouldGetCami) +{ + char err[1000] = "err"; + m = mj_loadXML(file.c_str(), 0, err, 1000); + if (!m) + { + return -1; + } + + ci = getControlInterface(); + si = getSensorInterface(); + + int errCode = 0; + if(shouldInitCam) + { + errCode = initCameras(); + if(shouldGetCami) + { + cami = getCameraInterface(); + } + else + { + cami.count = 0; + } + } + + return errCode; +} + +int MujocoModelInstance::initCameras() +{ + int ncams = m->ncam; + for(int camIndex=0; camIndex()); + + guiErrCodes offscreenStatus = offscreenCam[camIndex]->init(this, MJ_OFFSCREEN); + if(offscreenStatus != NO_ERR) + { + // TODO better error message with code + return -2; + } + + offscreenCam[camIndex]->camType = mjCAMERA_FIXED; // only handling fixed type camera now. assuming all cameras in xml as fixed type! + offscreenCam[camIndex]->camId = camIndex; + } + return 0; +} + +int MujocoModelInstance::initData() +{ + char err[1000] = "err"; + d = mj_makeData(m); + if(!d) return -1; + else return 0; +} + +MujocoModelInstance::~MujocoModelInstance() +{ + mj_deleteModel(m); + mj_deleteData(d); +} + +mjModel *MujocoModelInstance::get_m() +{ + return m; +} + +mjData *MujocoModelInstance::get_d() +{ + return d; +} + +double MujocoModelInstance::getSampleTime() +{ + return m->opt.timestep; +} + +sensorInterface MujocoModelInstance::getSensorInterface() +{ + sensorInterface si; + si.count = m->nsensor; + + std::vector names; + for (unsigned index = 0; index < si.count; index++) + { + char *namePointer = m->names + m->name_sensoradr[index]; + std::string str(namePointer); + names.push_back(str); + } + si.names = names; + + std::vector dim; + si.scalarCount = 0; + for (unsigned index = 0; index < si.count; index++) + { + unsigned sensor_dim = m->sensor_dim[index]; + dim.push_back(sensor_dim); + si.scalarCount += sensor_dim; + } + si.dim = dim; + + std::vector addr; + for (unsigned index = 0; index < si.count; index++) + { + unsigned sensor_addr = m->sensor_adr[index]; + addr.push_back(sensor_addr); + } + si.addr = addr; + return si; +} + +controlInterface MujocoModelInstance::getControlInterface() +{ + controlInterface ci; + ci.count = m->nu; + + std::vector names; + for (unsigned index = 0; index < ci.count; index++) + { + char *namePointer = m->names + m->name_actuatoradr[index]; + std::string str(namePointer); + names.push_back(str); + } + ci.names = names; + return ci; +} + +cameraInterface MujocoModelInstance::getCameraInterface() +{ + // CALL THIS FUNCTION ONLY FROM MAIN THREAD (MAC) OR THE THREAD THAT HANDLES THE REST OF RENDERING GLFW OPENGL WORK + // Run init and initCameras before running this + cameraInterface camiTemp; + camiTemp.count = offscreenCam.size(); + + std::vector names; + for(unsigned index=0; indexnames + m->name_camadr[index]; + std::string str(namePointer); + names.push_back(str); + } + camiTemp.names = names; + + unsigned long rgbAddr = 0; + unsigned long depthAddr = 0; + for(int index=0; indexinitInThread(&offSize, true); + // TODO handle error + camiTemp.size.push_back(offSize); + + camiTemp.rgbAddr.push_back(rgbAddr); + camiTemp.depthAddr.push_back(depthAddr); + rgbAddr += 3*offSize.height*offSize.width; // location of next rgb or length of rgb stored so far + depthAddr += offSize.height*offSize.width; + } + camiTemp.rgbLength = rgbAddr; + camiTemp.depthLength = depthAddr; + + return camiTemp; +} + +void MujocoModelInstance::step(std::vector u) +{ + // same memory location will be accessed during gui rendering + dMutex.lock(); + for (unsigned index = 0; index < u.size(); index++) + { + d->ctrl[index] = u[index]; + } + mj_step(m, d); + dMutex.unlock(); +} + +std::vector MujocoModelInstance::getSensor(unsigned index) +{ + std::vector sensorData; + + dMutex.lock(); + auto addrStart = si.addr[index]; + auto addrEnd = addrStart + si.dim[index] - 1; + for (unsigned i = addrStart; i <= addrEnd; i++) + { + sensorData.push_back(d->sensordata[i]); + } + dMutex.unlock(); + return sensorData; +} + +void forcopy(uint8_t *to, uint8_t *from, size_t size) +{ + for(size_t index = 0; index mutLock(camiMutex); + // if cami.count is 0, either no camera is in model or camera is not initialized + size_t addr = 0; + for(int index=0; index mutLock(offscreenCam[index]->camBufferMutex); + memcpy(buffer+addr, offscreenCam[index]->rgb, rgbArraySize*sizeof(uint8_t)); + // forcopy(buffer+addr, offscreenCam[index]->rgb, rgbArraySize); + } + addr += rgbArraySize; + } + return addr; +} + +void MujocoModelInstance::getCameraDepth(float *buffer) +{ + std::lock_guard mutLock(camiMutex); + unsigned long addr = 0; + for(int index=0; index mutLock(offscreenCam[index]->camBufferMutex); + memcpy(buffer+addr, offscreenCam[index]->depth, size*sizeof(float)); + } + addr += size; + } +} + +// GUI rendering ------------------------------------------------------------------ + +guiErrCodes MujocoGUI::init(std::shared_ptr mdlInstance, glTarget openglTarget) +{ + return init(mdlInstance.get(), openglTarget); +} + +static int err; +static char des[1000]; +void glfwFailCallback(int error, const char* description) +{ + err = error; + strncpy(des, description, sizeof(des)); +} + +guiErrCodes MujocoGUI::init(MujocoModelInstance* mdlInstance, glTarget openglTarget) +{ + // sets some variables and initializes opengl. Window creation is done in thread init. + target = openglTarget; + + // sceneAssetModel resources are uploaded to GPU. Note that the different model instances can share the same GPU resources + sceneAssetModel = mdlInstance; + + if(target == MJ_OFFSCREEN) + { + addMi(mdlInstance); + } + + return NO_ERR; +} + +guiErrCodes MujocoGUI::initInThread(offscreenSize *offSize, bool stopAtOffScreenSizeCalc) +{ + std::lock_guard glLock (glfwMutex); //opengl is not threadsafe or reentrant. lock until it is safe to unlock + + glfwSetErrorCallback(&glfwFailCallback); + if(glfwInit() == 0) + { + exited = true; + // stop any further opengl work for this object + return GLFW_INIT_FAILED; + } + + if(target == MJ_WINDOW) + { + glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); + glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); + } + else if(target == MJ_OFFSCREEN) + { + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE); + } + else + { + exited = true; + return UNKNOWN_TARGET; + } + + window = glfwCreateWindow(800, 800, "Simulation", NULL, NULL); + if( !window ) + { + exited = true; + return WINDOW_CREATION_FAILED; + } + + glfwMakeContextCurrent(window); + + if(target == MJ_WINDOW) glfwSwapInterval(static_cast(isVsyncOn)); // turn vsync for on screen rendering + + // init all rendering stuff + mjv_defaultCamera(&cam); + if (target == MJ_OFFSCREEN) + { + cam.type = camType; + cam.fixedcamid = camId; + } + else + { + for(int i=0; i<3; i++) + { + cam.lookat[i] = sceneAssetModel->get_m()->stat.center[i]; + } + + cam.distance = (sceneAssetModel->get_m()->stat.extent)/zoomLevel; + cam.type = mjCAMERA_FREE; + } + + mjv_defaultOption(&opt); + mjv_defaultScene(&scn); + mjr_defaultContext(&con); + + // upload GPU assets + mjv_makeScene(sceneAssetModel->get_m(), &scn, 2000); + mjr_makeContext(sceneAssetModel->get_m(), &con, mjFONTSCALE_100); + + // Set target for the current context and verify the same + if (target == MJ_OFFSCREEN) + { + mjr_setBuffer(mjFB_OFFSCREEN, &con); + if(con.currentBuffer != mjFB_OFFSCREEN) + { + mjv_freeScene(&scn); + mjr_freeContext(&con); + glfwDestroyWindow(window); + exited = true; + return OFFSCREEN_TARGET_NOT_SUPPORTED; + } + + // allocate memory for RGB and depth buffers. + viewport = mjr_maxViewport(&con); + int W = viewport.width; + int H = viewport.height; + if(offSize) + { + offSize->height = viewport.height; + offSize->width = viewport.width; + if(stopAtOffScreenSizeCalc) + { + mjv_freeScene(&scn); + mjr_freeContext(&con); + glfwDestroyWindow(window); + return NO_ERR; + } + } + + // allocate rgb and depth buffers + rgb = (unsigned char*) MALLOC(3*W*H, 8); + depth = (float*) MALLOC(sizeof(float)*W*H, 8); + + if( !rgb || !depth ) + { + if(rgb) FREE(rgb); + if(depth) FREE(depth); + mjv_freeScene(&scn); + mjr_freeContext(&con); + glfwDestroyWindow(window); + + exited = true; + return RGBD_BUFFER_ALLOC_FAILED; + } + + } + + glfwMakeContextCurrent(NULL); + return NO_ERR; +} + +int MujocoGUI::loopInThread() +{ + if(exited == false) + { + { + std::lock_guard glLock (glfwMutex); + if(glfwWindowShouldClose(window)) + { + // If user clicks on X to close the visualization. Close opengl window but let the simulation continue. + releaseInThread(); + return -1; + } + + { + glfwMakeContextCurrent(window); + + // modelInstancesLock.lock(); + for(int index=0; index mutLock(camBufferMutex); + mjr_readPixels(rgb, depth, viewport, &con); + } + + glfwMakeContextCurrent(NULL); + } + } + return 0; + } + else + { + return -1; + } +} + +void MujocoGUI::releaseInThread() +{ + if(exited == false) + { + std::lock_guard glLock (glfwMutex); + if(rgb) FREE(rgb); + if(depth) FREE(depth); + + glfwMakeContextCurrent(window); + mjv_freeScene(&scn); + mjr_freeContext(&con); + glfwDestroyWindow(window); // automatically detaches if current + exited = true; + } +} + +MujocoGUI::~MujocoGUI() +{ + return; +} + +void MujocoGUI::refreshScene(MujocoModelInstance* mi) +{ + // refreshScene clears previous scene and adds mi to the new scene + + // retrieve the screen width and height from active window + if(target != MJ_OFFSCREEN) + { + glfwGetFramebufferSize(window, &viewport.width, &viewport.height); + } + mi->dMutex.lock(); // lock before using simulation data + mjv_updateScene(mi->get_m(), mi->get_d(), &opt, NULL, &cam, mjCAT_ALL, &scn); + mi->dMutex.unlock(); +} +void MujocoGUI::addGeomsToScene(MujocoModelInstance* mi) +{ + // adds additional dynamic bodies to the same visualization. + // This should help with visualizing parallel simulations on the same window + mi->dMutex.lock(); + mjv_addGeoms(mi->get_m(), mi->get_d(), &opt, NULL, mjCAT_DYNAMIC, &scn); + mi->dMutex.unlock(); +} + +void MujocoGUI::addMi(shared_ptr mdlInstance) +{ + // model instances that are to be rendered. + // the first instance is rendered with mjCAT_ALL option + // the later instances (if any) are rendered with mjCAT_DYNAMIC + addMi(mdlInstance.get()); +} + +void MujocoGUI::addMi(MujocoModelInstance* mdlInstance) +{ + modelInstancesLock.lock(); + mdlInstances.push_back(mdlInstance); + modelInstancesLock.unlock(); +} + +// INTERFACES ------------------------------------------------ +std::size_t sensorInterface::hash() +{ + using std::to_string; + std::string str; + + str += "count=" + to_string(count); + + str += "\nnames="; + for(auto& nameItem: names) str += nameItem + ","; + + str += "\ndim="; + for(auto& dimItem: dim) str += to_string(dimItem) + ","; + + str += "\naddr="; + for(auto& addrItem: addr) str += to_string(addrItem) + ","; + + std::hash hasher; + return hasher(str); +} + +std::size_t controlInterface::hash() +{ + using std::to_string; + std::string str; + + str += "count=" + to_string(count); + + str += "\nnames="; + for(auto& nameItem: names) str += nameItem + ","; + + std::hash hasher; + return hasher(str); +} + +std::size_t cameraInterface::hash() +{ + using std::to_string; + std::string str; + + str += to_string(count); + for(auto& nameItem: names) str += nameItem; + for(auto& item: size) + { + str += to_string(item.height); + str += to_string(item.width); + } + // The above uniquely determine camera size properties + // for(auto& item: depthAddr) str += to_string(item); + // for(auto& item: rgbAddr) str += to_string(item); + + std::hash hasher; + return hasher(str); +} \ No newline at end of file diff --git a/src/mj.hpp b/src/mj.hpp new file mode 100644 index 0000000..894dea4 --- /dev/null +++ b/src/mj.hpp @@ -0,0 +1,194 @@ +// Minimal C++ interface Wrapper over Mujoco C API + +// Refer to MuJoCo website to learn more about the API +// MuJoCo is a trademark of DeepMind + +// Copyright 2022-2023 The MathWorks, Inc. + +#pragma once +#include "mujoco/mujoco.h" +// #include "glfw3.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "semaphore.hpp" + +// using namespace std::chrono_literals; + +class sensorInterface +{ + public: + unsigned count; + unsigned scalarCount; // number of individual data count. imu and rangefinder would give 4 scalars + std::vector names; + std::vector dim; + std::vector addr; + + std::size_t hash(); +}; + +class controlInterface +{ + public: + unsigned count; + std::vector names; + + std::size_t hash(); +}; + +struct offscreenSize +{ + unsigned height; + unsigned width; +}; + +class cameraInterface +{ + public: + unsigned count = 0; + std::vector names; + std::vector size; + std::vector rgbAddr; + std::vector depthAddr; + unsigned long rgbLength; + unsigned long depthLength; + + std::size_t hash(); +}; + +class MujocoGUI; +class MujocoModelInstance +{ + // This class is not designed to be moved or copied +private: + mjData *d = NULL; + mjModel *m = NULL; + + int initCameras(); + + controlInterface getControlInterface(); + sensorInterface getSensorInterface(); + cameraInterface getCameraInterface(); + + // disable copy constructor + MujocoModelInstance(const MujocoModelInstance &mi); +public: + std::mutex dMutex; // mutex for model data access + binarySemp cameraSync; // semp for syncing main thread and render camera thread + + // cameras in the model instance + std::vector> offscreenCam; + + // enable default contructor + MujocoModelInstance() = default; + ~MujocoModelInstance(); + + int initMdl(std::string file, bool shouldInitCam = true, bool shouldGetCami = true); + int initData(); + + // Cache for internal usage + controlInterface ci; + sensorInterface si; + + // Camera interface gets initialized in background rendering thread. Protect it with mutex + cameraInterface cami; + std::mutex camiMutex; + + double getSampleTime(); + mjModel *get_m(); + mjData *get_d(); + + + // Camera timing + double lastRenderTime = 0; + double cameraRenderInterval = 0.020; + std::atomic isCameraDataNew = false; + + void step(std::vector u); + std::vector getSensor(unsigned index); + size_t getCameraRGB(uint8_t *buffer); + void getCameraDepth(float *buffer); +}; + +enum glTarget +{ + MJ_WINDOW = 0, + MJ_OFFSCREEN +}; +enum guiErrCodes +{ + NO_ERR = 0, + UNKNOWN_TARGET, + WINDOW_CREATION_FAILED, + OFFSCREEN_TARGET_NOT_SUPPORTED, + RGBD_BUFFER_ALLOC_FAILED, + GLFW_INIT_FAILED +}; + +class MujocoGUI +{ + // This class represents a GUI window or an offscreen buffer (used for rendering rgbd cameras) + // GUI window can be common to multiple model instances (can overlap parallel simulations) + + private: + mjvOption opt; + mjrContext con; + mjrRect viewport = {0, 0, 0, 0}; + glTarget target; + + // adding content to a scene based on current simulation state + void refreshScene(MujocoModelInstance* mdlInstance); + void addGeomsToScene(MujocoModelInstance* mdlInstance); + + public: + GLFWwindow *window; // exposed for window callback management + mjvCamera cam; // exposed for window callback management + mjvScene scn; + double zoomLevel = 1.0; + + // rendering asset/scene information + std::vector mdlInstances; + MujocoModelInstance* sceneAssetModel; + + // rgb and depth buffers for offscreen rendering + std::mutex camBufferMutex; + unsigned char* rgb = nullptr; + float* depth = nullptr; + + // camera spec + mjtCamera camType; + int camId; + + std::atomic exited = false; + std::mutex modelInstancesLock; + + // Timing + bool isVsyncOn = false; + std::chrono::microseconds renderInterval; + std::chrono::time_point lastRenderClockTime; + + // Initialization + guiErrCodes init(std::shared_ptr mdlInstance, glTarget target); + guiErrCodes init(MujocoModelInstance* mdlInstance, glTarget target); + void addMi(std::shared_ptr mdlInstance); + void addMi(MujocoModelInstance* mdlInstance); + + // Run these in a single background thread + guiErrCodes initInThread(offscreenSize *offSize = NULL, bool stopAtOffScreenSizeCalc = false); + int loopInThread(); + void releaseInThread(); + + private: + // block copy constructor (can lead to double free cases when copied/moved) + MujocoGUI(const MujocoGUI &g); + + public: + // Enable default constructior and destructor + MujocoGUI() = default; + ~MujocoGUI(); +}; diff --git a/src/mj_depth_near_far.cpp b/src/mj_depth_near_far.cpp new file mode 100644 index 0000000..d1d77c4 --- /dev/null +++ b/src/mj_depth_near_far.cpp @@ -0,0 +1,74 @@ +// Copyright 2022-2023 The MathWorks, Inc. +#include "mex.hpp" +#include "mexAdapter.hpp" +#include "MatlabDataArray.hpp" +#include "mj.hpp" + +class MexFunction: public matlab::mex::Function +{ + private: + std::shared_ptr matlabPtr = getEngine(); + std::ostringstream stream; + matlab::data::ArrayFactory af; + + public: + void operator() + (matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) + { + + using namespace matlab::data; + using namespace matlab::mex; + using namespace matlab::engine; + + if(inputs.size() != 1) + { + printError("1 input expected"); + } + + std::string pathStr; + if(inputs[0].getType() == ArrayType::CHAR) + { + CharArray path = inputs[0]; + pathStr = path.toAscii(); + } + else + { + printError("Only char array allowed as input"); + } + + MujocoModelInstance mi; + if(mi.initMdl(pathStr, false) != 0) + { + printError("Unable to load and init file"); + } + + // https://github.com/deepmind/dm_control/blob/20cef21e2554592cd7fad0bb32c169aff2fe72bc/dm_control/mujoco/engine.py#L861 + // Based on, + // http://stackoverflow.com/a/6657284/1461210 + // https://www.khronos.org/opengl/wiki/Depth_Buffer_Precision + double extent = mi.get_m()->stat.extent; + double znear = mi.get_m()->vis.map.znear*extent; + double zfar = mi.get_m()->vis.map.zfar*extent; + + outputs[0] = af.createScalar(znear); + outputs[1] = af.createScalar(zfar); + + } + + void displayOnMATLAB(std::ostringstream& stream) + { + // Pass stream content to MATLAB fprintf function + matlabPtr->feval(u"fprintf", 0, std::vector({ af.createScalar(stream.str()) })); + // Clear stream buffer + stream.str(""); + } + + void printError(std::string err) + { + matlabPtr->feval(u"error", 0, + std::vector( + { af.createScalar(err) })); + } + +}; + diff --git a/src/mj_initbus_mex.cpp b/src/mj_initbus_mex.cpp new file mode 100644 index 0000000..968eef5 --- /dev/null +++ b/src/mj_initbus_mex.cpp @@ -0,0 +1,240 @@ +// This mex function generates a Simulink bus using "Simulink.Bus.createObject" +// 1. The generated bus is named uniquely using std::hash +// 2. If a bus with same name already exists, it will not regenerate +// 3. std::hash is assumed to return unique hash within a MATLAB instance + +// MATLAB and Simulink are registered trademarks of The MathWorks, Inc. +// Copyright 2022-2023 The MathWorks, Inc. + +#include "mex.hpp" +#include "mexAdapter.hpp" +#include "MatlabDataArray.hpp" +#include "mj.hpp" +#include +#include + +static std::mutex mut; + +class MexFunction: public matlab::mex::Function +{ + private: + std::shared_ptr matlabPtr = getEngine(); + std::ostringstream stream; + matlab::data::ArrayFactory af; + + public: + + void operator() + (matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) + { + // serialize this function as it accesses files and base workspace + // Donot want one call of this function to delete a temporary base workspace variable while another instance is still using it + + std::lock_guard lock(mut); + + using namespace matlab::data; + using namespace matlab::mex; + using namespace matlab::engine; + + if(inputs.size() != 1) + { + printError("Expected 1 input"); + } + + std::string pathStr; + if(inputs[0].getType() == ArrayType::CHAR) + { + CharArray path = inputs[0]; + pathStr = path.toAscii(); + } + else + { + printError("Only char array allowed as input"); + } + + std::shared_ptr mi = std::make_shared(); + if(mi->initMdl(pathStr) != 0) + { + printError("Unable to load file"); + } + + int outputIndex = 0; + + // input bus + auto ci = mi->ci; + std::string controlBusStr = inputBusGen(ci); + outputs[outputIndex++] = af.createCharArray(controlBusStr); + + // sensor bus + auto si = mi->si; + std::string sensorBusStr = sensorBusGen(si); + outputs[outputIndex++] = af.createCharArray(sensorBusStr); + + // rgb bus + auto cami = mi->cami; + std::string rgbBusStr = rgbBusGen(cami); + outputs[outputIndex++] = af.createCharArray(rgbBusStr); + + // depth bus + std::string depthBusStr = depthBusGen(cami); + outputs[outputIndex++] = af.createCharArray(depthBusStr); + + // data length output + ArrayDimensions lengthOutputdim{4, 1}; + outputs[outputIndex++] = af.createArray(lengthOutputdim, {ci.count, si.scalarCount, cami.rgbLength, cami.depthLength}); + + // displayOnMATLAB(stream); + + glfwTerminate(); + } + + std::string sensorBusGen(sensorInterface si) + { + using namespace matlab::data; + using namespace matlab::mex; + using namespace matlab::engine; + + // struct generation + StructArray busStruct = af.createStructArray({1}, si.names); + for(unsigned int index=0; index(sensorDim); + } + + // name the bus with a identifier unique to block outputs/inputs + std::string busStr="mj_bus_sensor_"; + busStr += std::to_string(si.hash()); + + return busGen(busStr, busStruct); + } + + std::string inputBusGen(controlInterface ci) + { + using namespace matlab::data; + using namespace matlab::mex; + using namespace matlab::engine; + + // struct generation + StructArray busStruct = af.createStructArray({1}, ci.names); + for(unsigned int index=0; index({1,1}); + } + + // name the bus with a identifier unique to block outputs/inputs + std::string busStr="mj_bus_input_"; + busStr += std::to_string(ci.hash()); + + return busGen(busStr, busStruct); + } + + std::string rgbBusGen(cameraInterface cami) + { + using namespace matlab::data; + using namespace matlab::mex; + using namespace matlab::engine; + + // struct generation + StructArray busStruct = af.createStructArray({1}, cami.names); + for(unsigned int index=0; index(outputDim); + } + + // name the bus with a identifier unique to block outputs/inputs + std::string busStr="mj_bus_rgb_"; + busStr += std::to_string(cami.hash()); + + return busGen(busStr, busStruct); + } + + std::string depthBusGen(cameraInterface cami) + { + using namespace matlab::data; + using namespace matlab::mex; + using namespace matlab::engine; + + // struct generation + StructArray busStruct = af.createStructArray({1}, cami.names); + for(unsigned int index=0; index(outputDim); + } + + // name the bus with a identifier unique to block outputs/inputs + std::string busStr="mj_bus_depth_"; + busStr += std::to_string(cami.hash()); + + return busGen(busStr, busStruct); + } + + std::string busGen(std::string busStr, matlab::data::StructArray busStruct) + { + using namespace matlab::data; + using namespace matlab::mex; + using namespace matlab::engine; + + // bus generation from struct + auto outputStream = std::shared_ptr(); + auto errorStream = std::shared_ptr(); + + // check for existence of bus + std::vector args({af.createScalar(busStr)}); + + std::vector result; + result = matlabPtr->feval(u"mj_busExist", 1, args, outputStream, errorStream); + + matlab::data::TypedArray returnedValues(std::move(result[0])); + double alreadyExistsDouble = returnedValues[0]; + + int alreadyExists = static_cast(std::round(alreadyExistsDouble)); + + if (!alreadyExists) + { + // If exists, do not generate the bus again + auto arg = std::vector({busStruct}); + auto outputVector = matlabPtr->feval(u"Simulink.Bus.createObject", 1, arg, outputStream, errorStream); + + matlab::data::StructArray outputStructArray = outputVector[0]; + + auto val = outputStructArray[0]["busName"]; + if(val.getType() == ArrayType::CHAR) + { + CharArray valChar = val; + std::string clearCmd = "clear('" + valChar.toAscii() + "');"; + auto busTempVariable = matlabPtr->getVariable(valChar.toAscii(), WorkspaceType::BASE); + matlabPtr->setVariable(busStr, busTempVariable, WorkspaceType::BASE); + matlabPtr->eval(convertUTF8StringToUTF16String("evalin base "+clearCmd)); + } + else + { + printError("busName field has to be a char array"); + } + } + return busStr; + } + + void displayOnMATLAB(std::ostringstream& stream) + { + // Pass stream content to MATLAB fprintf function + matlabPtr->feval(u"fprintf", 0, std::vector({ af.createScalar(stream.str()) })); + // Clear stream buffer + stream.str(""); + } + + void printError(std::string err) + { + matlabPtr->feval(u"error", 0, + std::vector( + { af.createScalar(err) })); + } + +}; + diff --git a/src/mj_sampletime.cpp b/src/mj_sampletime.cpp new file mode 100644 index 0000000..35bf542 --- /dev/null +++ b/src/mj_sampletime.cpp @@ -0,0 +1,68 @@ +// Copyright 2022-2023 The MathWorks, Inc. +#include "mex.hpp" +#include "mexAdapter.hpp" +#include "MatlabDataArray.hpp" +#include "mj.hpp" + +class MexFunction: public matlab::mex::Function +{ + private: + std::shared_ptr matlabPtr = getEngine(); + std::ostringstream stream; + matlab::data::ArrayFactory af; + + public: + void operator() + (matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) + { + + using namespace matlab::data; + using namespace matlab::mex; + using namespace matlab::engine; + + if(inputs.size() != 1) + { + printError("1 input expected"); + } + + std::string pathStr; + if(inputs[0].getType() == ArrayType::CHAR) + { + CharArray path = inputs[0]; + pathStr = path.toAscii(); + } + else + { + printError("Only char array allowed as input"); + } + + MujocoModelInstance mi; + if(mi.initMdl(pathStr, false) != 0) + { + printError("Unable to load file"); + } + + // sensor bus + auto sampleTime = mi.getSampleTime(); + outputs[0] = af.createScalar(sampleTime); + + // displayOnMATLAB(stream); + } + + void displayOnMATLAB(std::ostringstream& stream) + { + // Pass stream content to MATLAB fprintf function + matlabPtr->feval(u"fprintf", 0, std::vector({ af.createScalar(stream.str()) })); + // Clear stream buffer + stream.str(""); + } + + void printError(std::string err) + { + matlabPtr->feval(u"error", 0, + std::vector( + { af.createScalar(err) })); + } + +}; + diff --git a/src/mj_sfun.cpp b/src/mj_sfun.cpp new file mode 100644 index 0000000..f4ee2d0 --- /dev/null +++ b/src/mj_sfun.cpp @@ -0,0 +1,644 @@ +// Copyright 2022-2023 The MathWorks, Inc. +#define S_FUNCTION_NAME mj_sfun +#define S_FUNCTION_LEVEL 2 +#include "simstruc.h" + +#include "mj.hpp" +#include +#include +#include +#include +#include +#include + +// CONSTANT LIMITS +#define FILE_PATH_LIMIT 1000 +#define PORT_STRING_LMT 1000 +#define ERROR_LMT 100 +#define PARAM_STRING_LIMIT 100 + +/* S-Function parameter indices */ +typedef enum { + XML_PARAM_INDEX = 0, + RENDERING_INDEX, + CONTROL_LENGTH_INDEX, + SENSOR_LENGTH_INDEX, + RGB_LENGTH_INDEX, + DEPTH_LENGTH_INDEX, + VSYNC_INDEX, + VISUAL_FPS_INDEX, + CAMERA_SAMPLETIME_INDEX, + BLOCK_SAMPLETIME_INDEX, + ZOOM_LEVEL_INDEX, + PARAM_COUNT +} paramIdx; + +// Input indices +typedef enum { + CONTROL_PORT_INDEX = 0, + INPORT_COUNT +} inportIndex; + +// output indices +typedef enum { + SENSOR_PORT_INDEX = 0, + RGB_PORT_INDEX, + DEPTH_PORT_INDEX, + OUTPORT_COUNT +} outportIndex; + +typedef enum +{ + MI_IW_IDX=0, + MG_IW_IDX, + IWORK_COUNT +}iWorkIndex; + +typedef enum +{ + RENDERING_LOCAL=0, + RENDERING_GLOBAL, + RENDERING_NONE +}renderingTypeEnum; + +using std::vector; +using std::mutex; +using std::shared_ptr; +using std::make_shared; +void renderingThreadFcn(); + +// Mutexes inside mujocoModelInstance cannot be copied or moved. So address of mi is stored and moved inside vector +class _StaticData +{ + public: + vector> mi; + mutex miInitMutex; // used only during initialization + + vector> mg; + mutex mgInitMutex; + + guiErrCodes renderingInitErr = NO_ERR; + mutex renderingInitErrMutex; + + std::thread renderingThread; + std::atomic renderingThreadStarted = false; + std::atomic signalThreadExit = false; + + // Window management + bool leftButton = false; + bool rightButton = false; + double lastMouseX = 0; + double lastMouseY = 0; + + public: + void deleter() + { + // clear the blocks memory after each simulation + mg.clear(); + mi.clear(); + renderingInitErr = NO_ERR; + renderingThreadStarted = false; + signalThreadExit = false; + + leftButton = false; + rightButton = false; + lastMouseX = 0; + lastMouseY = 0; + } +} sd; +std::mutex sdMutex; // use it only when deleting the whole static data + +std::atomic activeSimulinkBlocksCount{0}; + +/* mi and mg data within are protected already by mutexes + But the vector as a whole is not protected. + Make sure mi and mg vector are protected during the phase they can change (init) +*/ + +// END OF STATIC/GLOBAL Variables--------------------------- + + +// WINDOW CALLBACK MANAGEMENT --------------------------- +// Based on MuJoCo's sample code basic.cc + +// Mouse callbacks +int getActiveWindowIndex(GLFWwindow* window) +{ + int activeGuiIndex = 0; + for(int i=0; iwindow == window) + { + activeGuiIndex = i; + break; + } + } + return activeGuiIndex; +} + +static void mouseMoveCallback(GLFWwindow* window, double x, double y) +{ + int activeGuiIndex = getActiveWindowIndex(window); + // If mouse just moves, do not do anything + if(!sd.leftButton && !sd.rightButton) + { + return; + } + + double dx = x - sd.lastMouseX; + double dy = y - sd.lastMouseY; + sd.lastMouseX = x; + sd.lastMouseY = y; + + bool shiftKeyStatus = (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT)==GLFW_PRESS || glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT)==GLFW_PRESS); + + int width, height; + glfwGetWindowSize(window, &width, &height); + + mjtMouse action; + if(sd.rightButton) + { + action = shiftKeyStatus ? mjMOUSE_MOVE_H : mjMOUSE_MOVE_V; + } + else if(sd.leftButton) + { + action = shiftKeyStatus ? mjMOUSE_ROTATE_H : mjMOUSE_ROTATE_V; + } + else + { + action = mjMOUSE_ZOOM; + } + + auto &gui = sd.mg[activeGuiIndex]; + mjv_moveCamera(gui->sceneAssetModel->get_m(), action, dx/height, dy/height, &gui->scn, &gui->cam); +} + +static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) +{ + sd.leftButton = (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT)==GLFW_PRESS); + sd.rightButton = (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT)==GLFW_PRESS); + glfwGetCursorPos(window, &sd.lastMouseX, &sd.lastMouseY); + + // Nothing else to be done for mouse clicks +} + +static void scrollCallback(GLFWwindow *window, double xoffset, double yoffset) +{ + int activeGuiIndex = getActiveWindowIndex(window); + auto &gui = sd.mg[activeGuiIndex]; + mjv_moveCamera(gui->sceneAssetModel->get_m(), mjMOUSE_ZOOM, 0, -0.05*yoffset, &gui->scn, &gui->cam); +} + +// void glfwCallbackInit() +// { +// glfwSetCursorPosCallback(window, NULL); +// glfwSetMouseButtonCallback(window, NULL); +// glfwSetScrollCallback(window, NULL); +// } + +// void window_focus_callback(GLFWwindow* window, int focused) +// { +// if (focused) +// { +// getActiveWindowIndex(window); +// // The window gained input focus +// glfwSetCursorPosCallback(window, mouseMoveCallback); +// glfwSetMouseButtonCallback(window, mouseButtonCallback); +// glfwSetScrollCallback(window, scrollCallback); +// } +// else +// { +// // The window lost input focus +// sd.activeGuiIndex = 0; + +// } +// } + +// HELPERS ----------------------------------------------------- +std::string getXmlFilePath(SimStruct *S) +{ + const mxArray *fileMexPtr = ssGetSFcnParam(S, XML_PARAM_INDEX); + char file[FILE_PATH_LIMIT]; + mxGetString(fileMexPtr, file, FILE_PATH_LIMIT-1); + return std::string(file); +} + +int getIntParam(SimStruct *S, int index) +{ + const mxArray *mexPtr = ssGetSFcnParam(S, index); + double param = mxGetScalar(mexPtr); + return static_cast(param); +} + +// MODEL INIT --------------------------------------------------------- +static void mdlInitializeSizes(SimStruct *S) +{ + // ssPrintf("mdlInitializeSizes entered\n"); + + //BASIC PARAMETERS -------------------------------------------------------------------------------- + // parameter sizes + ssSetNumSFcnParams(S, (int)PARAM_COUNT); + + if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) { + if (ssGetErrorStatus(S) != NULL) { + return; + } + return; /* Parameter mismatch will be reported by Simulink */ + } + + // sample times + ssSetNumSampleTimes(S, 1); + + /* specify the sim state compliance to be same as a built-in block */ + ssSetOperatingPointCompliance(S, OPERATING_POINT_COMPLIANCE_UNKNOWN); + + /* Set this S-function as runtime thread-safe for multicore execution */ + ssSetRuntimeThreadSafetyCompliance(S, RUNTIME_THREAD_SAFETY_COMPLIANCE_TRUE ); + + // Set all parameter as non-tunable + for(int index = 0; index()); + int miIndex = sd.mi.size()-1; + // mi will not be reduced or reordered, so mutex can be unlocked. + sd.miInitMutex.unlock(); + + // MODEL INIT + if(sd.mi[miIndex]->initMdl(file, true, false) != 0) + { + ssSetLocalErrorStatus(S,"Unable to initialize model in mdlStart"); + return; + } + + // MODEL DATA INIT + if(sd.mi[miIndex]->initData() != 0) + { + ssSetLocalErrorStatus(S,"Unable to initialize model instance data in mdlStart"); + return; + } + + { + // INIT CAMERA RENDER INTERVAL + const mxArray *paramMx = ssGetSFcnParam(S, CAMERA_SAMPLETIME_INDEX); + double cameraSampleTime = mxGetScalar(paramMx); + sd.mi[miIndex]->cameraRenderInterval = cameraSampleTime; + } + + ssSetIWorkValue(S, MI_IW_IDX, miIndex); + + // VISUALIZATION SETUP + const mxArray *renderingTypeMx = ssGetSFcnParam(S, RENDERING_INDEX); + char renderingTypeStr[PARAM_STRING_LIMIT]; + mxGetString(renderingTypeMx, renderingTypeStr, PARAM_STRING_LIMIT-1); + + renderingTypeEnum renderingType; + + if(strcmp(renderingTypeStr, "Local") == 0) renderingType = RENDERING_LOCAL; + else if(strcmp(renderingTypeStr, "Global") == 0) renderingType = RENDERING_GLOBAL; + else renderingType = RENDERING_NONE; + + sd.mgInitMutex.lock(); + if(renderingType == RENDERING_LOCAL || renderingType == RENDERING_GLOBAL) + { + int mgIndex = 0; + if(sd.mg.size() == 0 || renderingType == RENDERING_LOCAL) + { + sd.mg.push_back(make_shared()); + mgIndex = sd.mg.size()-1; + + guiErrCodes guiStatus = sd.mg[mgIndex]->init(sd.mi[miIndex], MJ_WINDOW); + + { + // SETUP VSYNC + const mxArray *paramMx = ssGetSFcnParam(S, VSYNC_INDEX); + double vsync = mxGetScalar(paramMx); + sd.mg[mgIndex]->isVsyncOn = (static_cast(vsync) == 1) ? true : false; + } + + { + // SETUP ZOOM LEVEL + const mxArray *paramMx = ssGetSFcnParam(S, ZOOM_LEVEL_INDEX); + double zoomLevel = mxGetScalar(paramMx); + sd.mg[mgIndex]->zoomLevel = zoomLevel; + } + + { + // SETUP FPS + const mxArray *paramMx = ssGetSFcnParam(S, VISUAL_FPS_INDEX); + double fps = mxGetScalar(paramMx); + std::chrono::milliseconds renderInterval{static_cast(1000.0/fps)}; + sd.mg[mgIndex]->renderInterval = renderInterval; + } + + if(guiStatus != NO_ERR) + { + static std::string err = "Unable to initialize GUI in mdlStart. Error code=" + std::to_string(guiStatus); + ssSetLocalErrorStatus(S, err.c_str()); // do not pass char array that may go out of its lifetime + return; + } + sd.mg[mgIndex]->addMi(sd.mi[miIndex]); // add to the list + } + else + { + sd.mg[mgIndex]->addMi(sd.mi[miIndex]); // add to the list + } + ssSetIWorkValue(S, MG_IW_IDX, mgIndex); + } + else + { + // do nothing. no rendering to be done + ssSetIWorkValue(S, MG_IW_IDX, -1); + } + sd.mgInitMutex.unlock(); + +} + +#define MDL_UPDATE +static void mdlUpdate(SimStruct *S, int_T tid) +{ + if(!sd.renderingThreadStarted) + { + sd.renderingThreadStarted = true; + sd.renderingThread = std::thread(renderingThreadFcn); + } + + // progress simulation by 1 time step in discrete time + int miIndex = ssGetIWorkValue(S, MI_IW_IDX); + + vector uVec; + int_T nInputs = ssGetInputPortWidth(S, CONTROL_PORT_INDEX) - 1; // last index is a dummy + InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S, CONTROL_PORT_INDEX); + for (int i = 0; i < nInputs; i++) + { + uVec.push_back((double) *uPtrs[i]); + } + + auto &miTemp = sd.mi[miIndex]; + + if(miTemp->offscreenCam.size() != 0) + { + if( (miTemp->get_d()->time - miTemp->lastRenderTime) > miTemp->cameraRenderInterval) + { + // maintain camera and physics in sync at required camera sample time + miTemp->cameraSync.acquire(); // blocking till offscreen buffer is rendered + miTemp->lastRenderTime = miTemp->get_d()->time; + } + } + + // set control inputs + miTemp->step(uVec); +} + +void renderingThreadFcn() +{ + // should run only after all initialization is done for mg. + + // Do opengl or glfw calls only in this thread. Or its gonna crash/error out. + + // TODO - + // Ideally glfw calls is to be made from main thread. In case of linux and windows, + // even using a single thread other than main should work. + // But in mac I expect this workflow to break (normal simulation) + + // I am not sure about the thread MATLAB uses to execute this s function + + // MODEL CAMERA INIT + for(int miIndex=0; miIndex mutLock(miTemp->camiMutex); + + cameraInterface &camiTemp = miTemp->cami; + camiTemp.count = miTemp->offscreenCam.size(); + // names are not needed here in s function. + + unsigned long rgbAddr = 0; + unsigned long depthAddr = 0; + for(int camIndex = 0; camIndexoffscreenCam[camIndex]->initInThread(&offSize); + if(guiStatus != NO_ERR) + { + std::lock_guard renderingInitErrLock(sd.renderingInitErrMutex); + sd.renderingInitErr = guiStatus; + // STOP SIMULATION HERE ITSELF! + // lets not stop simulation due to a rendering issue. + // throw a warning at the end of simulation + // TODO - Donot know how to stop simulation here. (that works in both codegen and normal mode) + } + camiTemp.size.push_back(offSize); + camiTemp.rgbAddr.push_back(rgbAddr); + camiTemp.depthAddr.push_back(depthAddr); + rgbAddr += 3*offSize.height*offSize.width; // location of next rgb or length of rgb stored so far + depthAddr += offSize.height*offSize.width; + } + camiTemp.rgbLength = rgbAddr; + camiTemp.depthLength = depthAddr; + } + + // INIT GUI windows + for(int index = 0; indexinitInThread(); + if(guiStatus != NO_ERR) + { + std::lock_guard renderingInitErrLock(sd.renderingInitErrMutex); + sd.renderingInitErr = guiStatus; + // lets not stop simulation due to a rendering issue. + // throw a warning at the end of simulation + } + glfwSetCursorPosCallback(sd.mg[index]->window, mouseMoveCallback); + glfwSetMouseButtonCallback(sd.mg[index]->window, mouseButtonCallback); + glfwSetScrollCallback(sd.mg[index]->window, scrollCallback); + } + + + // Visualization window and offscreen buffer rendering loop + while(1) + { + // Visualization window(s) + int activeCount = 0; + for(int index=0; indexlastRenderClockTime; + if (duration>sd.mg[index]->renderInterval) + { + if(sd.mg[index]->loopInThread() == 0) + { + activeCount++; + sd.mg[index]->lastRenderClockTime = std::chrono::steady_clock::now(); + } + } + } + + // Offscreen buffers + for(int miIndex=0; miIndexcameraSync.check_availability() == false) + { + // if rendering is already done and not consumed, dont do again + for(int camIndex = 0; camIndexoffscreenCam.size(); camIndex++) + { + if(miTemp->offscreenCam[camIndex]->loopInThread() == 0) + { + activeCount++; + miTemp->isCameraDataNew = true; + } + } + miTemp->cameraSync.release(); + } + } + // If there is nothing to render, donot keep spinning while loop + if(sd.signalThreadExit == true) break; + // if(activeCount == 0) break; + } + + // Release visualization resources + for(int index=0; indexreleaseInThread(); + } + + // Release offscreen buffers + for(int miIndex=0; miIndexoffscreenCam.size(); camIndex++) + { + sd.mi[miIndex]->offscreenCam[camIndex]->releaseInThread(); + } + } + + glfwTerminate(); +} + +static void mdlOutputs(SimStruct *S, int_T tid) +{ + int miIndex = ssGetIWorkValue(S, MI_IW_IDX); + + // Copy sensors to output + real_T *y = ssGetOutputPortRealSignal(S, SENSOR_PORT_INDEX); + int_T ny = ssGetOutputPortWidth(S, SENSOR_PORT_INDEX); + int_T index = 0; + + auto nSensors = sd.mi[miIndex]->si.count; + for(int_T i=0; i yVec = sd.mi[miIndex]->getSensor(i); + for(auto elem: yVec) + { + y[index] = elem; + index++; + // TODO add a check in case index exceeds the allowed limit + } + } + y[index] = static_cast(nSensors); // last element is a dummy to handle empty sensor case + + // Copy camera to output + uint8_T *rgbOut = (uint8_T *) ssGetOutputPortSignal(S, RGB_PORT_INDEX); + real32_T *depthOut = (real32_T *) ssGetOutputPortSignal(S, DEPTH_PORT_INDEX); + if(sd.mi[miIndex]->isCameraDataNew) + { + //avoid unnecessary memcpy. copy only when there is new data. Rest of the time steps, old data will be output + sd.mi[miIndex]->getCameraRGB((uint8_t *) rgbOut); + sd.mi[miIndex]->getCameraDepth((float *) depthOut); + sd.mi[miIndex]->isCameraDataNew = false; + } +} + +static void mdlTerminate(SimStruct *S) +{ + sd.signalThreadExit = true; + if(sd.renderingThread.joinable()) sd.renderingThread.join(); + + std::lock_guard lockSD (sdMutex); + activeSimulinkBlocksCount--; + if(activeSimulinkBlocksCount == 0) + { + sd.renderingInitErrMutex.lock(); + if (sd.renderingInitErr != NO_ERR) + { + std::string err = "Rendering has failed in initInThread(). Error code is "; + err += std::to_string(static_cast(sd.renderingInitErr)) + "\n"; + ssWarning(S, err.c_str()); + } + sd.renderingInitErrMutex.unlock(); + + sd.deleter(); + } +} + +#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */ +#include "simulink.c" /* MEX-file interface mechanism */ +#else +#include "cg_sfun.h" /* Code generation registration function */ +#endif diff --git a/src/semaphore.hpp b/src/semaphore.hpp new file mode 100644 index 0000000..817afc4 --- /dev/null +++ b/src/semaphore.hpp @@ -0,0 +1,41 @@ +// Copyright 2022-2023 The MathWorks, Inc. +#include +#include + +class binarySemp +{ + public: + + bool check_availability() + { + std::unique_lock locker(mut); + return state; + } + + void acquire() // blocking call + { + // locker maintains the mutex - automatically freeing after "out of scope" + std::unique_lock locker(mut); + + // Wait suspends this thread. locker unlocks the mutex while doing so. + // wakes up only after notify is received from another thread + // sometimes spurious wake ups happen, which we check using state == true function. + cv.wait(locker, [this](){ return state == true;}); + + // Once state is available or freed somewhere, consume it off. + state = false; + } + + void release() + { + std::unique_lock locker(mut); + state = true; + locker.unlock(); // mutex lock not needed anymore. not sure why we have to explicitly call this. + cv.notify_one(); + } + + private: + std::mutex mut; + std::condition_variable cv; + bool state = false; // 0 means it is blocked by someone +}; \ No newline at end of file diff --git a/src/workspace.code-workspace b/src/workspace.code-workspace new file mode 100644 index 0000000..4021340 --- /dev/null +++ b/src/workspace.code-workspace @@ -0,0 +1,88 @@ +{ + "folders": [ + { + "path": ".." + } + ], + "settings": { + "files.associations": { + "*.sdf": "xml", + "*.world": "xml", + "vector": "cpp", + "algorithm": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "format": "cpp", + "forward_list": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "utility": "cpp", + "xfacet": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstddef": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp", + "codecvt": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "resumable": "cpp", + "functional": "cpp", + "future": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "xhash": "cpp", + "cstdarg": "cpp", + "fstream": "cpp" + } + } +} \ No newline at end of file diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..12634e2 --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,2 @@ +win64.mk +glnxa64.mk \ No newline at end of file diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..36f51d3 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,88 @@ +# Use gmake to run this makefile + +# if you get permission error while building mex. Run >>clear mex in MATLAB command window + +# Copyright 2022-2023 The MathWorks, Inc. + +# USER CONFIG STARTS --------------------------------------------------------------------------- + +ifeq ($(OS),Windows_NT) +## WINDOWS LOCAL BUILD CONFIG +include ./win64.mk +MEX="$(ML_ROOT)/bin/mex.bat" +else +## LINUX LOCAL BUILD CONFIG +include ./glnxa64.mk +MEX="$(ML_ROOT)/bin/mex" +endif + +GLFW_IMPORTLIB_PATH=$(SET_GLFW_IMPORTLIB_PATH) +# USER CONFIG ENDS --------------------------------------------------------------------------- + +# IN CASE MJ_PATH is empty, it is local build. else it is gitlab-ci build +ifeq ($(MJ_PATH),) +MJ_PATH:=$(SET_MJ_PATH) +MEX_SETUP:=$(SET_MEX_SETUP) +else +MEX_SETUP:= +endif + +ifeq ($(ML_ROOT),) +ML_ROOT:=$(SET_ML_ROOT) +endif + +ifeq ($(ARCH),) +ARCH:=$(SET_ARCH) +endif + +# OUTPUT +OUT_DIR=../blocks + +# COMPILER INCLUDE AND SRC +SRC_PATH=../src +INC_PATH=-I$(MJ_PATH)/include/mujoco -I$(MJ_PATH)/include -I$(SRC_PATH) $(SET_GLFW_INC_PATH) +SRC_COMMON=$(SRC_PATH)/mj.cpp + +SRC_FILES := $(wildcard $(SRC_PATH)/*.cpp) +SRC_FILES := $(filter-out $(SRC_COMMON), $(SRC_FILES)) + +TARGET_FILES := $(subst .cpp,,$(SRC_FILES)) +TARGET_FILES := $(subst $(SRC_PATH)/,,$(TARGET_FILES)) + +# LINKER AND CPP CONFIG +LINKER_OBJ_WIN_COMMON=$(GLFW_IMPORTLIB_PATH)/glfw3dll.lib $(MJ_PATH)/lib/$(MJ_LIB_NAME).lib +ifeq ($(WIN_COMPILER), MINGW) +# LINKER_OBJ_WIN=-ldl $(LINKER_OBJ_WIN_COMMON) +LINKER_OBJ_WIN=$(LINKER_OBJ_WIN_COMMON) +CPP_FLAG=CXXFLAGS="-std=c++17" +LINKFLAGS=LINKFLAGS="-Wl,--no-undefined" +endif +ifeq ($(WIN_COMPILER), MSVCPP) +LINKER_OBJ_WIN=$(LINKER_OBJ_WIN_COMMON) +CPP_FLAG=COMPFLAGS="/EHsc /std:c++17" +LINKFLAGS= +endif + +LINKER_OBJ_LINUX=-L$(MJ_PATH)/lib -l$(MJ_LIB_NAME) -lGL -lglfw + +# MEX COMMAND +BUILD_CMD_COMMON=$(MEX) $(SRC_COMMON) $(INC_PATH) -outdir $(OUT_DIR) + +ifeq ($(ARCH), win64) +BUILD_CMD=$(BUILD_CMD_COMMON) $(CPP_FLAG) $(LINKER_OBJ_WIN) $(LINKFLAGS) +endif + +ifeq ($(ARCH), glnxa64) +BUILD_CMD=$(BUILD_CMD_COMMON) CXXFLAGS="-std=c++17 -fPIC" $(LINKER_OBJ_LINUX) LINKFLAGS="-Wl,--no-undefined" LDFLAGS="\$$LDFLAGS -Wl,-rpath,\\\$$ORIGIN" +endif + +DEBUG_FLAG=-g +setup: + $(MEX_SETUP) + +$(TARGET_FILES): + echo --------------------------------------; + $(BUILD_CMD) $(SRC_PATH)/$@.cpp $(DEBUG_FLAG) -output $@ + +build: $(TARGET_FILES) +.PHONY: debug build setup $(TARGET_FILES) diff --git a/tools/build.m b/tools/build.m new file mode 100644 index 0000000..6da5214 --- /dev/null +++ b/tools/build.m @@ -0,0 +1,10 @@ +% Copyright 2022-2023 The MathWorks, Inc. + +% Run mex -setup c++ and select a C++ compiler before running this file + +clear mex %#ok +if ispc + !gmake build +else + !make build +end diff --git a/tools/fetchLink.js b/tools/fetchLink.js new file mode 100644 index 0000000..ec3bd71 --- /dev/null +++ b/tools/fetchLink.js @@ -0,0 +1,16 @@ +// Copyright 2022-2023 The MathWorks, Inc. +json=require('./links.json'); +let name=process.env.THIRD_PARTY_NAME; +let version=process.env.THIRD_PARTY_VERSION +let arch=process.env.ARCH; +let link; +for(let index in json.files) +{ + let obj =json.files[index]; + if(obj.name===name && obj.version==version && obj.arch===arch) + { + link=obj["downloadLink"]; + break; + } +} +console.log(link); \ No newline at end of file diff --git a/tools/links.json b/tools/links.json new file mode 100644 index 0000000..12e3a18 --- /dev/null +++ b/tools/links.json @@ -0,0 +1,40 @@ +{ + "files": [ + { + "name": "mujoco", + "arch": "win64", + "version": "2.1.5", + "downloadLink": "https://github.com/deepmind/mujoco/releases/download/2.1.5/mujoco-2.1.5-windows-x86_64.zip" + }, + { + "name": "mujoco", + "arch": "glnxa64", + "version": "2.1.5", + "downloadLink": "https://github.com/deepmind/mujoco/releases/download/2.1.5/mujoco-2.1.5-linux-x86_64.tar.gz" + }, + { + "name": "mujoco", + "arch": "maci64", + "version": "2.1.5", + "downloadLink": "https://github.com/deepmind/mujoco/releases/download/2.1.5/mujoco-2.1.5-macos-universal2.dmg" + }, + { + "name": "mujoco", + "arch": "win64", + "version": "2.3.2", + "downloadLink": "https://github.com/deepmind/mujoco/releases/download/2.3.2/mujoco-2.3.2-windows-x86_64.zip" + }, + { + "name": "mujoco", + "arch": "glnxa64", + "version": "2.3.2", + "downloadLink": "https://github.com/deepmind/mujoco/releases/download/2.3.2/mujoco-2.3.2-linux-x86_64.tar.gz" + }, + { + "name": "glfw", + "arch": "win64", + "version": "3.3.7", + "downloadLink": "https://github.com/glfw/glfw/releases/download/3.3.7/glfw-3.3.7.bin.WIN64.zip" + } + ] +} \ No newline at end of file diff --git a/tools/package.m b/tools/package.m new file mode 100644 index 0000000..2f68b75 --- /dev/null +++ b/tools/package.m @@ -0,0 +1,39 @@ +%% Package into zip + +% folder name, {files} +fileList = { + 'blocks', {'*.xml', '*.m', '*.mex*', '*.pdb', '*.slx'} + 'examples', {'*.slx'} + 'src', {'*'} + 'tools', {'*.m', '*.js', '*.mk', '*.json', 'Makefile'} + '.', {'*.m', '*.txt', '*.md'} + }; + +curDir = pwd; +cd .. + +% Delete the package folder +MJ_VER = getpref('mujoco', 'MJ_VER'); +packageFolder = ['Release_', computer('arch'), '_', version('-release'), '_', MJ_VER]; +status = rmdir(['../', packageFolder], 's'); + +% Copy all files and folders to package folder +copyFiles(['../', packageFolder], fileList) + +cd(curDir) + +function copyFiles(packageFolder, fileList) +failCount = 0; +for folderIndex=1:length(fileList(:,1)) + folder = fileList{folderIndex, 1}; + for fileIndex=1:length(fileList{folderIndex, 2}) + files = fileList{folderIndex, 2}; + file = files{fileIndex}; + status = copyfile(fullfile(folder, file), fullfile(packageFolder, folder)); + if status == 0 + failCount = failCount+1; + end + end +end +disp(failCount) +end \ No newline at end of file diff --git a/tools/setupBuild.m b/tools/setupBuild.m new file mode 100644 index 0000000..2cc7239 --- /dev/null +++ b/tools/setupBuild.m @@ -0,0 +1,56 @@ +function setupBuild() +%% Makefile configuration for standalone build +% Copyright 2022-2023 The MathWorks, Inc. + +%% Compiler Setup +selectedCompilerWin="MSVCPP"; +% selectedCompilerWin="MINGW"; + +% Linux uses gcc for build + +%% Open MEX Configuration file +mlRoot = getUnixPath(matlabroot); +fileID = fopen(fullfile(computer('arch') +".mk"),'w'); + +%% MEX Build Information +fprintf(fileID, "#Autogenerated from MATLAB\n"); +fprintf(fileID, "SET_ARCH=%s\n", computer('arch')); +fprintf(fileID, "SET_ML_ROOT=%s\n", mlRoot); +if ispc + fprintf(fileID, "WIN_COMPILER=%s\n", selectedCompilerWin); + fprintf(fileID, "SET_GLFW_INC_PATH=-I%s\n", getUnixPath( getpref('mujoco', 'glfwIncPath'))); + fprintf(fileID, "SET_GLFW_IMPORTLIB_PATH=%s\n", getUnixPath( getpref('mujoco', 'glfwImportLibPath')) ); +end +disp('compiler selection and GLFW inc/import path setup done') +mjLibName = "mujoco"; +fprintf(fileID, "MJ_LIB_NAME=%s\n", mjLibName); +fprintf(fileID, "SET_MJ_PATH=../lib/%s/%s\n", computer('arch'), mjLibName); + +%% MEX Setup Configuration +% This information is not needed when ">>mex -setup c++" is manually called in MATLAB +% Command Window before calling "gmake build" +if ispc + if selectedCompilerWin=="MSVCPP" + compilerXML = "msvcpp2022.xml"; + elseif selectedCompilerWin=="MINGW" + compilerXML = "mingw64_g++_sdk10+.xml"; + end + fprintf(fileID, 'SET_MEX_SETUP="%s/bin/mex.bat" -setup:"%s/%s/%s"\n', mlRoot, mlRoot, "bin/win64/mexopts", compilerXML); +else + fprintf(fileID, "SET_MEX_SETUP=mex -setup c++\n"); +end + +%% Close file +fclose(fileID); + +%% Helpers + function unixPath = getUnixPath(winPath) + if ispc + % Using unix path even in windows + unixPath = join(split(winPath, '\'), '/'); + unixPath = unixPath{:}; + else + unixPath = winPath; + end + end +end \ No newline at end of file