🎮 Adds shadows, dynamic sliders and livestream to emulation
This commit is contained in:
@@ -14,7 +14,11 @@ import {
|
||||
MathUtils,
|
||||
LoaderUtils,
|
||||
GridHelper,
|
||||
Camera
|
||||
Camera,
|
||||
FogExp2,
|
||||
MeshBasicMaterial,
|
||||
CanvasTexture,
|
||||
CircleGeometry
|
||||
} from 'three';
|
||||
import { XacroLoader } from 'xacro-parser';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||
@@ -24,9 +28,11 @@ import { lerp } from '../../lib/utils';
|
||||
import uzip from 'uzip';
|
||||
import { outControllerData } from '../../lib/store';
|
||||
import Kinematic from '../../lib/kinematic';
|
||||
import location from '../../lib/location';
|
||||
|
||||
let el: HTMLCanvasElement;
|
||||
let scene: Scene, camera: Camera, renderer: WebGLRenderer, controls: OrbitControls, robot;
|
||||
let canvas: HTMLCanvasElement, streamCanvas: HTMLCanvasElement, stream: HTMLImageElement, scene: Scene, camera: Camera, renderer: WebGLRenderer, controls: OrbitControls, robot, isLoaded = false;
|
||||
|
||||
let context: CanvasRenderingContext2D, texture: CanvasTexture
|
||||
|
||||
let modelAngles:number[] | Int8Array = new Array(12).fill(0)
|
||||
let modelTargetAngles:number[] | Int8Array = new Array(12).fill(0)
|
||||
@@ -37,6 +43,11 @@ let modelTargeBodyAngles:EulerAngle = {omega: 0, phi: 0, psi: 0 }
|
||||
let modelBodyPoint:Point = {x: 0, y: 0, z: 0 }
|
||||
let modelTargetBodyPoint:Point = {x: 0, y: 0, z: 0 }
|
||||
|
||||
const dir = [ -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1]
|
||||
const videoStream = `//${location}/stream`;
|
||||
|
||||
let showModel = true, showStream = false
|
||||
|
||||
const servoNames = [
|
||||
"front_left_shoulder", "front_left_leg", "front_left_foot",
|
||||
"front_right_shoulder", "front_right_leg", "front_right_foot",
|
||||
@@ -83,13 +94,11 @@ const stand = () => {
|
||||
const calculateKinematics = () => {
|
||||
const kinematic = new Kinematic();
|
||||
const angles: number[] = [degToRad(modelTargeBodyAngles.omega), degToRad(modelTargeBodyAngles.phi), degToRad(modelTargeBodyAngles.psi)];
|
||||
const center: number[] = [modelBodyPoint.x, modelBodyPoint.y, modelBodyPoint.z];
|
||||
const center: number[] = [modelTargetBodyPoint.x, modelTargetBodyPoint.y, modelTargetBodyPoint.z];
|
||||
const Lp = [[100,-100,100,1],[100,-100,-100,1],[-100,-100,100,1],[-100,-100,-100,1]]
|
||||
|
||||
const legs = kinematic.calcIK(Lp, angles, center)
|
||||
|
||||
const dir = [ -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1]
|
||||
|
||||
const legsAngles = legs
|
||||
.map(x => x.map(y => radToDeg(y)))
|
||||
.flat()
|
||||
@@ -102,12 +111,16 @@ onMount(async () => {
|
||||
createScene()
|
||||
|
||||
outControllerData.subscribe(data => {
|
||||
modelTargeBodyAngles = {omega:(data[1]-128) / 3, phi:(data[2]-128) / 3, psi:(data[3]-128) / 4}
|
||||
modelTargeBodyAngles = {omega:0, phi:(data[1]-128) / 3, psi:(data[2]-128) / 4}
|
||||
modelTargetBodyPoint = {x:(data[4]-128) / 2, y:data[5], z:(data[3]-128) / 2} // (data[5]-128) / 4
|
||||
calculateKinematics()
|
||||
//modelTargetBodyPoint = data.bodyPoint
|
||||
})
|
||||
|
||||
servoBuffer.subscribe(angles => modelTargetAngles = angles)
|
||||
|
||||
modelTargeBodyAngles = {omega:0, phi:0, psi:0}
|
||||
modelTargetBodyPoint = {x:0, y:0, z:0}
|
||||
stand()
|
||||
});
|
||||
|
||||
const cacheModelFiles = async () => {
|
||||
@@ -136,6 +149,7 @@ const loadModel = () => {
|
||||
robot.rotation.z = Math.PI / 2;
|
||||
robot.traverse(c => c.castShadow = true);
|
||||
robot.updateMatrixWorld(true);
|
||||
robot.scale.setScalar(10);
|
||||
|
||||
scene.add( robot );
|
||||
|
||||
@@ -144,40 +158,51 @@ const loadModel = () => {
|
||||
|
||||
const createScene = () => {
|
||||
scene = new Scene();
|
||||
|
||||
camera = new PerspectiveCamera();
|
||||
camera.position.set(-0.5, 0.5, 1);
|
||||
|
||||
renderer = new WebGLRenderer({ antialias: true, canvas: el });
|
||||
renderer = new WebGLRenderer({ antialias: true, canvas: canvas, alpha: true });
|
||||
renderer.outputEncoding = sRGBEncoding;
|
||||
renderer.shadowMap.enabled = true;
|
||||
renderer.shadowMap.type = PCFSoftShadowMap;
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
const directionalLight = new DirectionalLight(0xffffff, 1.0);
|
||||
const directionalLight = new DirectionalLight(0xffffff, 0.9);
|
||||
directionalLight.castShadow = true;
|
||||
directionalLight.shadow.mapSize.setScalar(1024);
|
||||
directionalLight.position.set(5, 30, 5);
|
||||
directionalLight.shadow.mapSize.setScalar(2048);
|
||||
directionalLight.shadow.mapSize.width = 1024;
|
||||
directionalLight.shadow.mapSize.height = 1024;
|
||||
directionalLight.position.set(50, 100, 100);
|
||||
directionalLight.shadow.radius = 5
|
||||
scene.add(directionalLight);
|
||||
|
||||
const ambientLight = new AmbientLight(0xffffff, 0.2);
|
||||
const ambientLight = new AmbientLight(0xffffff, 0.3);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const ground = new Mesh(new PlaneGeometry(), new ShadowMaterial({ opacity: 0.25 }));
|
||||
if(!showStream) scene.fog = new FogExp2( 0xcccccc, 0.015 );
|
||||
|
||||
const ground = new Mesh( new PlaneGeometry(), new ShadowMaterial({side: 2}));
|
||||
ground.rotation.x = -Math.PI / 2;
|
||||
ground.scale.setScalar(30);
|
||||
ground.position.y = -2
|
||||
ground.receiveShadow = true;
|
||||
scene.add(ground);
|
||||
|
||||
const size = 10;
|
||||
const divisions = 50;
|
||||
context = streamCanvas.getContext("2d");
|
||||
texture = new CanvasTexture( stream );
|
||||
const liveStream = new Mesh( new CircleGeometry(35, 32), new MeshBasicMaterial({ map: texture }))
|
||||
liveStream.position.z = -50
|
||||
liveStream.visible = showStream
|
||||
scene.add(liveStream)
|
||||
|
||||
const gridHelper = new GridHelper(size, divisions);
|
||||
gridHelper.position.y = -0.24
|
||||
const gridHelper = new GridHelper(250, 125);
|
||||
gridHelper.position.y = -2;
|
||||
scene.add(gridHelper);
|
||||
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.minDistance = 0;
|
||||
controls.maxDistance = 4;
|
||||
controls.minDistance = 10;
|
||||
controls.maxDistance = 30;
|
||||
controls.update();
|
||||
|
||||
loadModel()
|
||||
@@ -199,6 +224,21 @@ const render = () => {
|
||||
|
||||
if(!robot) return
|
||||
|
||||
if(!isLoaded){
|
||||
const intervalId = setInterval(() => {
|
||||
robot.traverse(c => c.castShadow = true);
|
||||
}, 10);
|
||||
setTimeout(() => {
|
||||
clearInterval(intervalId)
|
||||
}, 1000);
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
if(isLoaded && showStream) {
|
||||
context.drawImage(stream, 0, 0)
|
||||
texture.needsUpdate = true;
|
||||
}
|
||||
|
||||
for (let i = 0; i < servoNames.length; i++) {
|
||||
modelAngles[i] = lerp(robot.joints[servoNames[i]].angle * (180/Math.PI), modelTargetAngles[i], 0.1)
|
||||
robot.joints[servoNames[i]].setJointValue(MathUtils.degToRad(modelAngles[i]));
|
||||
@@ -206,11 +246,7 @@ const render = () => {
|
||||
|
||||
modelBodyAngles.omega = lerp(robot.rotation.x * (180/Math.PI), modelTargeBodyAngles.omega - 90, 0.1)
|
||||
modelBodyAngles.phi = lerp(robot.rotation.y * (180/Math.PI), modelTargeBodyAngles.phi, 0.1)
|
||||
modelBodyAngles.psi = lerp(robot.rotation.z * (180/Math.PI), modelTargeBodyAngles.psi + 90, 0.1)
|
||||
|
||||
// robot.rotation.x = MathUtils.degToRad(modelBodyAngles.omega)
|
||||
// robot.rotation.y = MathUtils.degToRad(modelBodyAngles.phi)
|
||||
// robot.rotation.z = MathUtils.degToRad(modelBodyAngles.psi)
|
||||
modelBodyAngles.psi = lerp(robot.rotation.z * (180/Math.PI), modelTargeBodyAngles.psi + 90, 0.1)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -225,18 +261,18 @@ const render = () => {
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<h1 class="text-on-background text-xl mt-4">Motor angles</h1>
|
||||
{#each servoNames as name, i}
|
||||
{#each Object.entries(robot?.joints ?? {}).filter(x => x[1].jointValue.length > 0) as [name, joint], i}
|
||||
<div class="flex justify-between mb-2">
|
||||
<span class="w-40">{name}: </span>
|
||||
<input type="range" min="-180" max="180" step="0.1" class="accent-primary" bind:value={$servoBuffer[i]}>
|
||||
<input class="w-24 bg-background" min="-180" max="180" step="0.1" bind:value={$servoBuffer[i]}>
|
||||
<input type="range" min="{radToDeg(joint.limit.lower)}" max="{radToDeg(joint.limit.upper)}" step="0.1" class="accent-primary" bind:value={$servoBuffer[i]}>
|
||||
<input class="w-24 bg-background" min="{radToDeg(joint.limit.lower)}" max="{radToDeg(joint.limit.upper)}" step="0.1" bind:value={$servoBuffer[i]}>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="text-on-background text-xl mb-2">Body rotation</h1>
|
||||
{#each Object.entries(modelBodyAngles) as [name, angle]}
|
||||
{#each Object.keys(modelBodyAngles) as name}
|
||||
<div class="flex justify-between mb-2">
|
||||
<span class="w-40">{name}: </span>
|
||||
<input type="range" min="-180" max="180" step="0.1" class="accent-primary" bind:value={modelTargeBodyAngles[name]} on:input={calculateKinematics}>
|
||||
@@ -247,7 +283,7 @@ const render = () => {
|
||||
|
||||
<div>
|
||||
<h1 class="text-on-background text-xl mb-2">Body position</h1>
|
||||
{#each Object.entries(modelBodyPoint) as [name, coordinate]}
|
||||
{#each Object.keys(modelBodyPoint) as name}
|
||||
<div class="flex justify-between mb-2">
|
||||
<span class="w-40">{name}: </span>
|
||||
<input type="range" min="-180" max="180" step="0.1" class="accent-primary" bind:value={modelTargetBodyPoint[name]} on:input={calculateKinematics}>
|
||||
@@ -257,4 +293,14 @@ const render = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<canvas bind:this={el} class="absolute"></canvas>
|
||||
{#if showStream}
|
||||
<img
|
||||
bind:this={stream}
|
||||
src={videoStream}
|
||||
class="hidden"
|
||||
alt="Live stream is down"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
{/if}
|
||||
<canvas bind:this={streamCanvas} class="hidden"></canvas>
|
||||
<canvas bind:this={canvas} class="absolute"></canvas>
|
||||
@@ -48,7 +48,7 @@
|
||||
<button>
|
||||
<Icon src={Power} size="32" />
|
||||
</button>
|
||||
<button class:text-blue-600={$emulateModel} on:click={() => emulateModel.update(v => {v = !v; return v})}>
|
||||
<button class:text-blue-600={$emulateModel} on:click={() => emulateModel.update(v => !v)}>
|
||||
<Icon src={Cube} size="32"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user