Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions EnvironmentSimulator/Libraries/esminiJS/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ set(EXPR_INCLUDE_DIR
set(FMT_INCLUDE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/../../../externals/fmt/include")

if(DEFINED
FMT_OVERRIDE_INCLUDE_DIR)
set(FMT_INCLUDE_DIR
"${FMT_OVERRIDE_INCLUDE_DIR}")
endif()

set(YAML_INCLUDE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/../../../externals/yaml")

Expand Down
64 changes: 54 additions & 10 deletions EnvironmentSimulator/Libraries/esminiJS/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
## esminijs
- 只是一个例子,不是完整的功能(Just an example, not full functionality)
- 只是一个例子,不是完整的功能(Just an example, not full functionality)
- 只是一个例子,不是完整的功能(Just an example, not full functionality)
## esminiJS

## points of attention
- 1、每个文件在读取之前都需要关联,resources 中包含的资源不可以被使用(Each file needs to be associated before reading, and the resources contained in resources cannot be used)
- 2、功能未充分测试(Functionality not fully tested)
This directory contains the standalone Emscripten/WebAssembly wrapper for esmini.

Status:

- intended as the starting point for browser integrations
- not yet a complete web visualization solution
- currently focused on simulation stepping and data access, not rendering

## Points of attention

- Scenario files and referenced assets must be mounted into the Emscripten filesystem before loading them.
- The browser build does not include the native OSG viewer path.
- The wrapper is still evolving and should be treated as a browser-facing engine API, not a finalized SDK.
- The build script applies a repo-owned compatibility patch to a copied `fmt` header tree under `build/`, so the `externals/fmt` submodule does not need to be edited locally.

## Install
Tested on Windows 10 (from CMD), macOS (Catalina) and Linux (Ubuntu 22.04, glic 2.35, gcc/g++ 11.3)
Expand Down Expand Up @@ -109,6 +117,42 @@ cd ..
dir example\esmini.js
```

* Load examples/index.html in browser
- The following text should appear: `Will not use threejs, please see the console output` (no graphics to expect)
- Check console for info printouts, e.g. Ego position
* Load `example/index.html` in a browser
- The page should load the `esmini.js` runtime successfully
- Use the demo buttons to step a scenario and inspect object state output in the page log

## Browser-facing API

The wrapper now exposes a live simulation API in addition to the older bulk snapshot helpers.

Main methods on `OpenScenario`:

- `step(dt)` advances the simulation once and returns the engine status code
- `step_frame(dt)` advances once and returns a frame object with simulation time and current object states
- `reset()` recreates the scenario engine from the original `.xosc`
- `get_road_geometry()` returns sampled lane-surface strips and road-mark polylines for browser rendering
- `get_object_states()` returns the current active objects
- `get_object_state_by_index(index)` returns one current object state
- `get_object_count()` returns the current active object count
- `get_simulation_time()` returns current simulation time in seconds
- `is_quit()` reports whether the scenario has completed

Configuration values exposed through embind `value_object` types are passed from JavaScript as plain object literals, for example:

```js
const scenario = new module.OpenScenario("/example.xosc", {
max_loop: 0,
min_time_step: 0,
max_time_step: 0,
dt: 0.05,
});
```

Compatibility helpers kept for the existing demo:

- `get_object_state(config)`
- `get_object_state_by_second(seconds, fps)`

These helpers still accumulate snapshots across multiple simulation steps, but new web clients should prefer the live stepping API.

The example directory now also contains a separate three.js viewer page that consumes `get_road_geometry()` and the live frame API.
70 changes: 64 additions & 6 deletions EnvironmentSimulator/Libraries/esminiJS/build.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,66 @@
if [ ! -d "build/" ]; then
mkdir "build/"
#!/usr/bin/env bash

set -euo pipefail

if ! command -v emcmake >/dev/null 2>&1; then
echo "emcmake not found. Activate emsdk before building esminiJS." >&2
exit 1
fi

script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd -- "$script_dir/../../.." && pwd)"
build_dir="${script_dir}/build"
example_dir="${script_dir}/example"
patched_fmt_root="${build_dir}/patched-externals/fmt"
patched_fmt_include_dir="${patched_fmt_root}/include"
fmt_patch="${repo_root}/scripts/patches/fmt-11.2.0-emscripten-cstdlib.patch"
user_em_config="${HOME}/.emscripten"
parallel_jobs=1

if [[ -f "$user_em_config" ]]; then
export EM_CONFIG="$user_em_config"
fi

if command -v nproc >/dev/null 2>&1; then
parallel_jobs="$(nproc)"
elif command -v sysctl >/dev/null 2>&1; then
parallel_jobs="$(sysctl -n hw.logicalcpu 2>/dev/null || printf '1')"
elif command -v getconf >/dev/null 2>&1; then
parallel_jobs="$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')"
fi

if [[ ! -f "$fmt_patch" ]]; then
echo "Expected fmt compatibility patch not found: $fmt_patch" >&2
exit 1
fi

if ! command -v patch >/dev/null 2>&1; then
echo "patch command not found. Install patch to prepare the WASM build includes." >&2
exit 1
fi

if [[ -f "$build_dir/CMakeCache.txt" ]]; then
cache_source_dir="$(sed -n 's/^CMAKE_HOME_DIRECTORY:INTERNAL=//p' "$build_dir/CMakeCache.txt" | head -n 1)"
if [[ -n "$cache_source_dir" && "$cache_source_dir" != "$script_dir" ]]; then
echo "Clearing stale CMake cache from $cache_source_dir"
rm -rf "$build_dir"
fi
fi

mkdir -p "$build_dir"
rm -rf "$patched_fmt_root"
mkdir -p "$patched_fmt_root"
cp -R "$repo_root/externals/fmt/include" "$patched_fmt_root/"

patch -s -N -p1 -d "$patched_fmt_root" < "$fmt_patch"

cd "$build_dir"
emcmake cmake -DCMAKE_BUILD_TYPE=Release -DFMT_OVERRIDE_INCLUDE_DIR="$patched_fmt_include_dir" ..
cmake --build . --parallel "$parallel_jobs"

cp -f esmini.js "$example_dir/"
if [[ -f esmini.wasm ]]; then
cp -f esmini.wasm "$example_dir/"
fi

cd build/
emcmake cmake ..
emmake make -j $(nproc)
cp esmini.js ../example/
echo "Built esminiJS artifacts in $build_dir and copied runtime files to $example_dir"
103 changes: 103 additions & 0 deletions EnvironmentSimulator/Libraries/esminiJS/embind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ namespace esmini
EMSCRIPTEN_BINDINGS(OpenScenario)
{
emscripten::register_vector<std::string>("vector<string>");
emscripten::register_vector<RoadGeometryPoint>("vector<RoadGeometryPoint>");
emscripten::register_vector<LaneSurfaceGeometry>("vector<LaneSurfaceGeometry>");
emscripten::register_vector<RoadMarkGeometry>("vector<RoadMarkGeometry>");
emscripten::register_vector<LaneCenterGeometry>("vector<LaneCenterGeometry>");
emscripten::register_vector<RoadObjectGeometry>("vector<RoadObjectGeometry>");
emscripten::register_vector<RoadFeatureBoxGeometry>("vector<RoadFeatureBoxGeometry>");
emscripten::register_vector<ScenarioObjectState>("vector<ScenarioObjectState>");

emscripten::value_object<OpenScenarioConfig>("OpenScenarioConfig")
Expand All @@ -35,6 +41,7 @@ namespace esmini
.field("t", &ScenarioObjectState::t)
.field("lane_id", &ScenarioObjectState::lane_id)
.field("lane_offset", &ScenarioObjectState::lane_offset)
.field("s", &ScenarioObjectState::s)
.field("speed", &ScenarioObjectState::speed)
.field("center_offset_x", &ScenarioObjectState::center_offset_x)
.field("center_offset_y", &ScenarioObjectState::center_offset_y)
Expand All @@ -44,11 +51,107 @@ namespace esmini
.field("height", &ScenarioObjectState::height)
.field("object_type", &ScenarioObjectState::object_type)
.field("object_category", &ScenarioObjectState::object_category)
.field("has_ghost", &ScenarioObjectState::has_ghost)
.field("sensor_x", &ScenarioObjectState::sensor_x)
.field("sensor_y", &ScenarioObjectState::sensor_y)
.field("sensor_z", &ScenarioObjectState::sensor_z)
.field("trail_x", &ScenarioObjectState::trail_x)
.field("trail_y", &ScenarioObjectState::trail_y)
.field("trail_z", &ScenarioObjectState::trail_z)
.field("wheel_angle", &ScenarioObjectState::wheel_angle)
.field("wheel_rot", &ScenarioObjectState::wheel_rot);

emscripten::value_object<ScenarioFrame>("ScenarioFrame")
.field("simulation_time", &ScenarioFrame::simulation_time)
.field("time_step", &ScenarioFrame::time_step)
.field("status", &ScenarioFrame::status)
.field("quit", &ScenarioFrame::quit)
.field("object_states", &ScenarioFrame::object_states);

emscripten::value_object<RoadGeometryPoint>("RoadGeometryPoint")
.field("s", &RoadGeometryPoint::s)
.field("x", &RoadGeometryPoint::x)
.field("y", &RoadGeometryPoint::y)
.field("z", &RoadGeometryPoint::z)
.field("h", &RoadGeometryPoint::h)
.field("p", &RoadGeometryPoint::p)
.field("r", &RoadGeometryPoint::r)
.field("endpoint", &RoadGeometryPoint::endpoint);

emscripten::value_object<LaneSurfaceGeometry>("LaneSurfaceGeometry")
.field("road_id", &LaneSurfaceGeometry::road_id)
.field("lane_section_index", &LaneSurfaceGeometry::lane_section_index)
.field("lane_id", &LaneSurfaceGeometry::lane_id)
.field("lane_type", &LaneSurfaceGeometry::lane_type)
.field("road_edge", &LaneSurfaceGeometry::road_edge)
.field("left_boundary", &LaneSurfaceGeometry::left_boundary)
.field("right_boundary", &LaneSurfaceGeometry::right_boundary);

emscripten::value_object<RoadMarkGeometry>("RoadMarkGeometry")
.field("road_id", &RoadMarkGeometry::road_id)
.field("lane_section_index", &RoadMarkGeometry::lane_section_index)
.field("lane_id", &RoadMarkGeometry::lane_id)
.field("type", &RoadMarkGeometry::type)
.field("color", &RoadMarkGeometry::color)
.field("width", &RoadMarkGeometry::width)
.field("line_length", &RoadMarkGeometry::line_length)
.field("line_space", &RoadMarkGeometry::line_space)
.field("points", &RoadMarkGeometry::points);

emscripten::value_object<LaneCenterGeometry>("LaneCenterGeometry")
.field("road_id", &LaneCenterGeometry::road_id)
.field("lane_section_index", &LaneCenterGeometry::lane_section_index)
.field("lane_id", &LaneCenterGeometry::lane_id)
.field("reference_line", &LaneCenterGeometry::reference_line)
.field("center_lane", &LaneCenterGeometry::center_lane)
.field("points", &LaneCenterGeometry::points);

emscripten::value_object<RoadObjectGeometry>("RoadObjectGeometry")
.field("road_id", &RoadObjectGeometry::road_id)
.field("id", &RoadObjectGeometry::id)
.field("name", &RoadObjectGeometry::name)
.field("type", &RoadObjectGeometry::type)
.field("width_start", &RoadObjectGeometry::width_start)
.field("width_end", &RoadObjectGeometry::width_end)
.field("height_start", &RoadObjectGeometry::height_start)
.field("height_end", &RoadObjectGeometry::height_end)
.field("points", &RoadObjectGeometry::points);

emscripten::value_object<RoadFeatureBoxGeometry>("RoadFeatureBoxGeometry")
.field("road_id", &RoadFeatureBoxGeometry::road_id)
.field("id", &RoadFeatureBoxGeometry::id)
.field("name", &RoadFeatureBoxGeometry::name)
.field("type", &RoadFeatureBoxGeometry::type)
.field("kind", &RoadFeatureBoxGeometry::kind)
.field("x", &RoadFeatureBoxGeometry::x)
.field("y", &RoadFeatureBoxGeometry::y)
.field("z", &RoadFeatureBoxGeometry::z)
.field("h", &RoadFeatureBoxGeometry::h)
.field("p", &RoadFeatureBoxGeometry::p)
.field("r", &RoadFeatureBoxGeometry::r)
.field("width", &RoadFeatureBoxGeometry::width)
.field("length", &RoadFeatureBoxGeometry::length)
.field("height", &RoadFeatureBoxGeometry::height);

emscripten::value_object<ScenarioRoadGeometry>("ScenarioRoadGeometry")
.field("odr_filename", &ScenarioRoadGeometry::odr_filename)
.field("lane_surfaces", &ScenarioRoadGeometry::lane_surfaces)
.field("road_marks", &ScenarioRoadGeometry::road_marks)
.field("lane_centers", &ScenarioRoadGeometry::lane_centers)
.field("road_objects", &ScenarioRoadGeometry::road_objects)
.field("road_feature_boxes", &ScenarioRoadGeometry::road_feature_boxes);

emscripten::class_<OpenScenario>("OpenScenario")
.constructor<std::string, OpenScenarioConfig>()
.function("step", &OpenScenario::step)
.function("step_frame", &OpenScenario::step_frame)
.function("reset", &OpenScenario::reset)
.function("get_object_count", &OpenScenario::get_object_count)
.function("get_object_states", &OpenScenario::get_object_states)
.function("get_object_state_by_index", &OpenScenario::get_object_state_by_index)
.function("get_road_geometry", &OpenScenario::get_road_geometry)
.function("get_simulation_time", &OpenScenario::get_simulation_time)
.function("is_quit", &OpenScenario::is_quit)
.function("get_object_state", &OpenScenario::get_object_state, emscripten::allow_raw_pointers())
.function("get_object_state_by_second", &OpenScenario::get_object_state_by_second);
}
Expand Down
Loading