logs in console, caching in getAgentProps, console createAgent independent of GUI
|
After Width: | Height: | Size: 2.6 MiB |
@@ -0,0 +1,11 @@
|
|||||||
|
Model Information:
|
||||||
|
* title: Mech Drone
|
||||||
|
* source: https://sketchfab.com/3d-models/mech-drone-8d06874aac5246c59edb4adbe3606e0e
|
||||||
|
* author: Willy Decarpentrie (https://sketchfab.com/skudgee)
|
||||||
|
|
||||||
|
Model License:
|
||||||
|
* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
|
||||||
|
* requirements: Author must be credited. Commercial use is allowed.
|
||||||
|
|
||||||
|
If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
|
||||||
|
This work is based on "Mech Drone" (https://sketchfab.com/3d-models/mech-drone-8d06874aac5246c59edb4adbe3606e0e) by Willy Decarpentrie (https://sketchfab.com/skudgee) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
|
||||||
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 6.9 MiB |
@@ -0,0 +1,40 @@
|
|||||||
|
//////////////TESTING /////////////////
|
||||||
|
this.threeClock = new THREE.Clock()
|
||||||
|
const loader = new GLTFLoader().setPath( '/app/assets/3dModels/' );
|
||||||
|
loader.load( 'mechdrone/scene.gltf', async function ( gltf ) {
|
||||||
|
|
||||||
|
const model = gltf.scene;
|
||||||
|
|
||||||
|
// wait until the model can be added to the scene without blocking due to shader compilation
|
||||||
|
|
||||||
|
await this.renderer.compileAsync( model, this.camera, this.scene );
|
||||||
|
model.scale.set(5,5,5)
|
||||||
|
this.scene.add( model )
|
||||||
|
model.traverse(obj => {
|
||||||
|
console.log(
|
||||||
|
obj.type,
|
||||||
|
obj.name || '(no name)',
|
||||||
|
obj.isBone ? '[BONE]' : ''
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if (obj.isMesh && obj.material && obj.material.color) {
|
||||||
|
const c = obj.material.color.clone()
|
||||||
|
const boost = 0.1 // réglage global
|
||||||
|
obj.material.emissive = c
|
||||||
|
obj.material.emissiveIntensity = boost
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.leftArm = model.getObjectByName('ArmL1_01')
|
||||||
|
|
||||||
|
if (gltf.animations && gltf.animations.length > 0) {
|
||||||
|
this.mixer = new THREE.AnimationMixer(gltf.scene)
|
||||||
|
// On joue la première animation
|
||||||
|
const action = this.mixer.clipAction(gltf.animations[0])
|
||||||
|
action.play()
|
||||||
|
}
|
||||||
|
}.bind(this) )
|
||||||
|
|
||||||
|
// In Render:
|
||||||
|
const delta = this.threeClock.getDelta()
|
||||||
|
if(this.mixer) this.mixer.update(delta)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<style>
|
<style>
|
||||||
.kf-editor .inner-console .results .api-summary{ border: 1px solid white;margin: .5em;width: 90%; }
|
.kf-editor .inner-console .results .api-summary{ border: 1px solid white;margin: .5em;width: 90%; border-radius: 7px;}
|
||||||
.kf-editor .inner-console .results .api-summary h1{
|
.kf-editor .inner-console .results .api-summary h1{
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
background: #DDD;
|
background: #DDD;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
.kf-editor .inner-console .results button[data-trigger="onExample"]{ margin-left: 2em; }
|
||||||
</style>
|
</style>
|
||||||
<div class="title">Javascript Keyframe console</div>
|
<div class="title">Javascript Keyframe console</div>
|
||||||
Use any combination of Javascript and API calls to update your keyframe scene.<br>
|
Use any combination of Javascript and API calls to update your keyframe scene.<br>
|
||||||
@@ -21,8 +22,32 @@ Special commands:<br>
|
|||||||
<div class="api-summary">
|
<div class="api-summary">
|
||||||
<h1>API:</h1>
|
<h1>API:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Create an agent: <b>newAgent(type, properties)</b> - returns the AID</li>
|
<li>Log here: <b>log()</b> - any number of arguments of any type</li>
|
||||||
<li>Remove an agent: <b>removeAgent(aid)</b></li>
|
<li>Create an agent: <b>createAgent(type, properties)</b> - Promise returning the AID <button eicbutton info xxsmall data-trigger="onSnippet" data-snippet="createAgent">Snippet</button></li>
|
||||||
<li>Update an agent: <b>updateAgent(aid, properties)</b></li>
|
<li>Remove an agent: <b>removeAgent(aid)</b> <button eicbutton info xxsmall data-trigger="onSnippet" data-snippet="removeAgent">Snippet</button></li>
|
||||||
|
<li>Update an agent: <b>updateAgent(aid, properties)</b> <button eicbutton info xxsmall data-trigger="onSnippet" data-snippet="updateAgent">Snippet</button></li>
|
||||||
|
<li>List agent types: <b>listAgentTypes()</b> <button eicbutton info xxsmall data-trigger="onSnippet" data-snippet="listAgentTypes">Snippet</button></li>
|
||||||
|
<li>List agents on scene: <b>listAgentsOnScene()</b> <button eicbutton info xxsmall data-trigger="onSnippet" data-snippet="listAgentsOnScene">Snippet</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="snippet" data-snippet="createAgent" style="display:none">
|
||||||
|
const agtIds = []
|
||||||
|
for(let i=0; i<360; i+=22.5){
|
||||||
|
agtIds.push(
|
||||||
|
await createAgent(1, { position: { x: 10*Math.sin(i*Math.PI/180), y: 10*Math.cos(i*Math.PI/180), z: 0 } } )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
log(agtIds)
|
||||||
|
</div>
|
||||||
|
<div class="snippet" data-snippet="removeAgent" style="display:none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="snippet" data-snippet="updateAgent" style="display:none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="snippet" data-snippet="listAgentTypes" style="display:none">
|
||||||
|
for(const agt of listAgentTypes() console.log(agt)
|
||||||
|
</div>
|
||||||
|
<div class="snippet" data-snippet="listAgentsOnScene" style="display:none">
|
||||||
|
for(const agt of listAgentsOnScene() console.log(agt)
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>three.js webgl - FBX loader</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||||
|
<link type="text/css" rel="stylesheet" href="https://threejs.org/examples/main.css">
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
||||||
|
"three/examples/jsm/loaders/FontLoader.js": "https://unpkg.com/three@0.160.0/examples/jsm/loaders/FontLoader.js",
|
||||||
|
"three/examples/jsm/geometries/TextGeometry.js": "https://unpkg.com/three@0.160.0/examples/jsm/geometries/TextGeometry.js",
|
||||||
|
"three/examples/jsm/loaders/RGBELoader.js": "https://unpkg.com/three@0.160.0/examples/jsm/loaders/RGBELoader.js",
|
||||||
|
"three/examples/jsm/controls/OrbitControls.js": "https://unpkg.com/three@0.160.0/examples/jsm/controls/OrbitControls.js",
|
||||||
|
"three/examples/jsm/libs/tween.module.js": "https://unpkg.com/three@0.160.0/examples/jsm/libs/tween.module.js",
|
||||||
|
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="info">
|
||||||
|
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - FBXLoader<br />
|
||||||
|
Character and animation from <a href="https://www.mixamo.com/" target="_blank" rel="noopener">Mixamo</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import Stats from 'three/addons/libs/stats.module.js';
|
||||||
|
|
||||||
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||||
|
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
|
||||||
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
||||||
|
|
||||||
|
const manager = new THREE.LoadingManager();
|
||||||
|
|
||||||
|
let camera, scene, renderer, stats, object, loader, guiMorphsFolder;
|
||||||
|
let mixer;
|
||||||
|
|
||||||
|
const clock = new THREE.Clock();
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
asset: 'Samba Dancing'
|
||||||
|
};
|
||||||
|
|
||||||
|
const assets = [
|
||||||
|
'Samba Dancing',
|
||||||
|
'morph_test',
|
||||||
|
'monkey',
|
||||||
|
'monkey_embedded_texture',
|
||||||
|
'vCube',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
|
||||||
|
const container = document.createElement( 'div' );
|
||||||
|
document.body.appendChild( container );
|
||||||
|
|
||||||
|
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
|
||||||
|
camera.position.set( 100, 200, 300 );
|
||||||
|
|
||||||
|
scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color( 0xa0a0a0 );
|
||||||
|
scene.fog = new THREE.Fog( 0xa0a0a0, 200, 1000 );
|
||||||
|
|
||||||
|
const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444, 5 );
|
||||||
|
hemiLight.position.set( 0, 200, 0 );
|
||||||
|
scene.add( hemiLight );
|
||||||
|
|
||||||
|
const dirLight = new THREE.DirectionalLight( 0xffffff, 5 );
|
||||||
|
dirLight.position.set( 0, 200, 100 );
|
||||||
|
dirLight.castShadow = true;
|
||||||
|
dirLight.shadow.camera.top = 180;
|
||||||
|
dirLight.shadow.camera.bottom = - 100;
|
||||||
|
dirLight.shadow.camera.left = - 120;
|
||||||
|
dirLight.shadow.camera.right = 120;
|
||||||
|
scene.add( dirLight );
|
||||||
|
|
||||||
|
// scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
|
||||||
|
|
||||||
|
// ground
|
||||||
|
const mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2000, 2000 ), new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } ) );
|
||||||
|
mesh.rotation.x = - Math.PI / 2;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
scene.add( mesh );
|
||||||
|
|
||||||
|
const grid = new THREE.GridHelper( 2000, 20, 0x000000, 0x000000 );
|
||||||
|
grid.material.opacity = 0.2;
|
||||||
|
grid.material.transparent = true;
|
||||||
|
scene.add( grid );
|
||||||
|
|
||||||
|
loader = new FBXLoader( manager );
|
||||||
|
loadAsset( params.asset );
|
||||||
|
|
||||||
|
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
||||||
|
renderer.setPixelRatio( window.devicePixelRatio );
|
||||||
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||||
|
renderer.setAnimationLoop( animate );
|
||||||
|
renderer.shadowMap.enabled = true;
|
||||||
|
container.appendChild( renderer.domElement );
|
||||||
|
|
||||||
|
const controls = new OrbitControls( camera, renderer.domElement );
|
||||||
|
controls.target.set( 0, 100, 0 );
|
||||||
|
controls.update();
|
||||||
|
|
||||||
|
window.addEventListener( 'resize', onWindowResize );
|
||||||
|
|
||||||
|
// stats
|
||||||
|
stats = new Stats();
|
||||||
|
container.appendChild( stats.dom );
|
||||||
|
|
||||||
|
const gui = new GUI();
|
||||||
|
gui.add( params, 'asset', assets ).onChange( function ( value ) {
|
||||||
|
|
||||||
|
loadAsset( value );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
guiMorphsFolder = gui.addFolder( 'Morphs' ).hide();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAsset( asset ) {
|
||||||
|
|
||||||
|
loader.load( 'models/ffbxbx/' + asset + '.fbx', function ( group ) {
|
||||||
|
|
||||||
|
if ( object ) {
|
||||||
|
|
||||||
|
object.traverse( function ( child ) {
|
||||||
|
|
||||||
|
if ( child.isSkinnedMesh ) {
|
||||||
|
|
||||||
|
child.skeleton.dispose();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( child.material ) {
|
||||||
|
|
||||||
|
const materials = Array.isArray( child.material ) ? child.material : [ child.material ];
|
||||||
|
materials.forEach( material => {
|
||||||
|
|
||||||
|
if ( material.map ) material.map.dispose();
|
||||||
|
material.dispose();
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( child.geometry ) child.geometry.dispose();
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
scene.remove( object );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
object = group;
|
||||||
|
|
||||||
|
if ( object.animations && object.animations.length ) {
|
||||||
|
|
||||||
|
mixer = new THREE.AnimationMixer( object );
|
||||||
|
|
||||||
|
const action = mixer.clipAction( object.animations[ 0 ] );
|
||||||
|
action.play();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
mixer = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
guiMorphsFolder.children.forEach( ( child ) => child.destroy() );
|
||||||
|
guiMorphsFolder.hide();
|
||||||
|
|
||||||
|
object.traverse( function ( child ) {
|
||||||
|
|
||||||
|
if ( child.isMesh ) {
|
||||||
|
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
|
||||||
|
if ( child.morphTargetDictionary ) {
|
||||||
|
|
||||||
|
guiMorphsFolder.show();
|
||||||
|
const meshFolder = guiMorphsFolder.addFolder( child.name || child.uuid );
|
||||||
|
Object.keys( child.morphTargetDictionary ).forEach( ( key ) => {
|
||||||
|
|
||||||
|
meshFolder.add( child.morphTargetInfluences, child.morphTargetDictionary[ key ], 0, 1, 0.01 );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
scene.add( object );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowResize() {
|
||||||
|
|
||||||
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
|
||||||
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
|
||||||
|
const delta = clock.getDelta();
|
||||||
|
|
||||||
|
if ( mixer ) mixer.update( delta );
|
||||||
|
|
||||||
|
renderer.render( scene, camera );
|
||||||
|
|
||||||
|
stats.update();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -31,7 +31,8 @@
|
|||||||
"/helpers/validators",
|
"/helpers/validators",
|
||||||
"/helpers/activeAttributes",
|
"/helpers/activeAttributes",
|
||||||
"/helpers/helpers3D.module",
|
"/helpers/helpers3D.module",
|
||||||
"/helpers/formBuilder"
|
"/helpers/formBuilder",
|
||||||
|
"/helpers/kfConsole"
|
||||||
],
|
],
|
||||||
"assets": {
|
"assets": {
|
||||||
"styles": [
|
"styles": [
|
||||||
|
|||||||
@@ -1,32 +1,77 @@
|
|||||||
if(!app.helpers) app.helpers = {}
|
if(!app.helpers) app.helpers = {}
|
||||||
/**
|
/**
|
||||||
* Mixing add-in methods to your view instance.
|
* Mixing add-in methods to your view instance.
|
||||||
* All of this should not be a helper, but inherited this from WindozDomContent, but not my framework anymore.
|
* All of this should not be a helper, but inherited this from EICDomContent, but not my framework anymore.
|
||||||
* @category MyEic
|
* @category MyEic
|
||||||
*/
|
*/
|
||||||
app.helpers.activeAttributes = {
|
app.helpers.activeAttributes = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* setupTriggers adds all click (data-trigger) and change (data-change) handlers.
|
* setupTriggers adds all click (data-trigger) and change (data-change) handlers.
|
||||||
* handlers should have the signatue : onXyz(component, event), will spit a warning if the handler doesn't exist.
|
* handlers should have the signatue : onXyz(component, event), will spit a warning if the handler doesn't exist.
|
||||||
* setupTriggers is re-entrant: it can be called again after refreshing part of the view
|
* setupTriggers is re-entrant: it can be called again after refreshing part of the view
|
||||||
* @param {eicui-components []} components : the view's components (usually result of ui.eicfy(this.el) )
|
* @param {eicui-components []} components : the view's components (usually result of ui.eicfy(this.el) )
|
||||||
*/
|
*/
|
||||||
setupTriggers(components){ // Should inherit this from WindozDomContent, but not my framework anymore.
|
_triggersRegister: { 'click': new WeakMap(), 'change': new WeakMap()},
|
||||||
for(let component of components.filter(component => component.el.hasAttribute('data-trigger'))) {
|
setupTriggers(components = []){ // Should inherit this from EICDomContent, but not my framework anymore.
|
||||||
|
for(let component of components.filter(component => component.el.hasAttribute('data-trigger'))) { //components with or without click property
|
||||||
if(typeof this[component.el.dataset.trigger] !== 'function') {
|
if(typeof this[component.el.dataset.trigger] !== 'function') {
|
||||||
console.warn(`data-trigger without corresponding method : ${component.el.dataset.trigger}`)
|
console.warn(`data-trigger without corresponding method : ${component.el.dataset.trigger}`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
component.click = this[component.el.dataset.trigger].bind(this, component)
|
if(component.click) component.click = this[component.el.dataset.trigger].bind(this, component)
|
||||||
|
else {
|
||||||
|
const oldTrigger = this._triggersRegister.click.get(component.el)
|
||||||
|
if(oldTrigger) component.el.removeEventListener('click', oldTrigger)
|
||||||
|
const newTrigger = this[component.el.dataset.trigger].bind(this, component)
|
||||||
|
this._triggersRegister.click.set(component.el, newTrigger)
|
||||||
|
component.el.addEventListener('click', newTrigger)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for(let component of components.filter(component => component.el.hasAttribute('data-change'))) {
|
if(this.el){ // for views and other content-based classes, add triggers on simple non-component elements
|
||||||
|
for(const el of this.el.querySelectorAll('[data-trigger]:not([data-eicui-id])')){
|
||||||
|
if(typeof this[el.dataset.trigger] !== 'function') {
|
||||||
|
console.warn(`data-trigger without corresponding method : ${el.dataset.trigger}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const oldTrigger = this._triggersRegister.click.get(el)
|
||||||
|
if(oldTrigger) el.removeEventListener('click', oldTrigger)
|
||||||
|
const newTrigger = this[el.dataset.trigger].bind(this)
|
||||||
|
this._triggersRegister.click.set(el, newTrigger)
|
||||||
|
el.addEventListener('click', newTrigger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let component of components.filter(component => component.el.hasAttribute('data-change'))) { //components with or without click property
|
||||||
if(typeof this[component.el.dataset.change] !== 'function') {
|
if(typeof this[component.el.dataset.change] !== 'function') {
|
||||||
console.warn(`data-change without corresponding method : ${component.el.dataset.trigger}`)
|
console.warn(`data-change without corresponding method : ${component.el.dataset.change}`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
component.el.addEventListener("change",this[component.el.dataset.change].bind(this, component))
|
const oldTrigger = this._triggersRegister.change.get(component.el)
|
||||||
if(component.el.type=='text') component.el.addEventListener("keyup",this[component.el.dataset.change].bind(this, component))
|
if(oldTrigger) {
|
||||||
|
component.el.removeEventListener('change', oldTrigger)
|
||||||
|
component.el.removeEventListener('keyup', oldTrigger)
|
||||||
|
}
|
||||||
|
const newTrigger = this[component.el.dataset.change].bind(this, component)
|
||||||
|
this._triggersRegister.change.set(component.el, newTrigger)
|
||||||
|
component.el.addEventListener("change", newTrigger)
|
||||||
|
if(component.el.type=='text') component.el.addEventListener("keyup", newTrigger)
|
||||||
|
}
|
||||||
|
if(this.el){ // for views and other content-based classes, add triggers on simple non-component elements
|
||||||
|
for(const el of this.el.querySelectorAll('[data-change]:not([data-eicui-id])')){
|
||||||
|
if(typeof this[el.dataset.change] !== 'function') {
|
||||||
|
console.warn(`data-change without corresponding method : ${el.dataset.change}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const oldTrigger = this._triggersRegister.change.get(el)
|
||||||
|
if(oldTrigger) {
|
||||||
|
el.removeEventListener('change', oldTrigger)
|
||||||
|
el.removeEventListener('keyup', oldTrigger)
|
||||||
|
}
|
||||||
|
const newTrigger = this[el.dataset.change].bind(this)
|
||||||
|
this._triggersRegister.change.set(el, newTrigger)
|
||||||
|
el.addEventListener('change', newTrigger)
|
||||||
|
if(el.type=='text') el.addEventListener('keyup', newTrigger)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -39,7 +84,7 @@ app.helpers.activeAttributes = {
|
|||||||
* setupRefs is re-entrant: it can be called again after refreshing part of the view
|
* setupRefs is re-entrant: it can be called again after refreshing part of the view
|
||||||
* @param {eicui-components []} components : the view's components (usually result of ui.eicfy(this.el) )
|
* @param {eicui-components []} components : the view's components (usually result of ui.eicfy(this.el) )
|
||||||
*/
|
*/
|
||||||
setupRefs(components = []){
|
setupRefs(components=[]){
|
||||||
if(!this.components) this.components = {}
|
if(!this.components) this.components = {}
|
||||||
for(let component of components.filter(component => component.el.hasAttribute('data-ref'))) {
|
for(let component of components.filter(component => component.el.hasAttribute('data-ref'))) {
|
||||||
this.components[component.el.dataset.ref] = component
|
this.components[component.el.dataset.ref] = component
|
||||||
@@ -61,7 +106,6 @@ app.helpers.activeAttributes = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* output (singular) : this.output('mydiv', '<p>Some markup</p>') places markup in a data-output node
|
* output (singular) : this.output('mydiv', '<p>Some markup</p>') places markup in a data-output node
|
||||||
* @param {*} name
|
* @param {*} name
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { PDBLoader } from 'three/examples/jsm/loaders/PDBLoader.js'
|
||||||
|
|
||||||
|
async function loadMolecule(url) {
|
||||||
|
const loader = new PDBLoader()
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
loader.load(
|
||||||
|
url,
|
||||||
|
pdb => {
|
||||||
|
// pdb contient:
|
||||||
|
// pdb.geometryAtoms : BufferGeometry des atomes
|
||||||
|
// pdb.geometryBonds : BufferGeometry des liaisons
|
||||||
|
// pdb.json : infos (numéro atomique, couleurs…)
|
||||||
|
|
||||||
|
const group = new THREE.Group()
|
||||||
|
|
||||||
|
// --- Atomes ---
|
||||||
|
const atomsGeo = pdb.geometryAtoms
|
||||||
|
const atomsInfo = pdb.json.atoms
|
||||||
|
|
||||||
|
for (let i = 0; i < atomsInfo.length; i++) {
|
||||||
|
const atom = atomsInfo[i]
|
||||||
|
|
||||||
|
const position = new THREE.Vector3(
|
||||||
|
atomsGeo.attributes.position.getX(i),
|
||||||
|
atomsGeo.attributes.position.getY(i),
|
||||||
|
atomsGeo.attributes.position.getZ(i)
|
||||||
|
)
|
||||||
|
|
||||||
|
const color = new THREE.Color(atom.color)
|
||||||
|
const radius = atom.radius
|
||||||
|
|
||||||
|
const sphere = new THREE.Mesh(
|
||||||
|
new THREE.SphereGeometry(radius, 32, 16),
|
||||||
|
new THREE.MeshPhongMaterial({ color })
|
||||||
|
)
|
||||||
|
|
||||||
|
sphere.position.copy(position)
|
||||||
|
group.add(sphere)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Liaisons ---
|
||||||
|
const bondsGeo = pdb.geometryBonds
|
||||||
|
|
||||||
|
for (let i = 0; i < bondsGeo.attributes.position.count; i += 2) {
|
||||||
|
const start = new THREE.Vector3().fromBufferAttribute(bondsGeo.attributes.position, i)
|
||||||
|
const end = new THREE.Vector3().fromBufferAttribute(bondsGeo.attributes.position, i + 1)
|
||||||
|
|
||||||
|
const bondLength = start.distanceTo(end)
|
||||||
|
|
||||||
|
const cyl = new THREE.Mesh(
|
||||||
|
new THREE.CylinderGeometry(0.1, 0.1, bondLength, 16),
|
||||||
|
new THREE.MeshPhongMaterial({ color: 0xffffff })
|
||||||
|
)
|
||||||
|
|
||||||
|
// Orienter et positionner le cylindre
|
||||||
|
cyl.position.copy(start).lerp(end, 0.5)
|
||||||
|
cyl.lookAt(end)
|
||||||
|
cyl.rotateX(Math.PI / 2)
|
||||||
|
|
||||||
|
group.add(cyl)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(group)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
reject
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
if(!app.helpers) app.helpers = {}
|
||||||
|
app.helpers.kfConsole = {
|
||||||
|
|
||||||
|
async execCommand(event){
|
||||||
|
if(this.outputs.commands.value.trim()=='\\help'){
|
||||||
|
this.outputs.commands.value = ''
|
||||||
|
this.outputs.results.innerHTML += await app.Assets.loadHtml({ name: 'help/KFconsoleHelp.html' })
|
||||||
|
this.setupTriggers()
|
||||||
|
} else if(this.outputs.commands.value.trim()=='\\clear'){
|
||||||
|
this.outputs.commands.value = ''
|
||||||
|
this.outputs.results.innerHTML = ''
|
||||||
|
} else {
|
||||||
|
this.outputs.results.innerHTML += await this.evalCmd(this.outputs.commands.value)
|
||||||
|
}
|
||||||
|
const lines = this.outputs.results.querySelectorAll('div.line')
|
||||||
|
if(lines.length > 100) {
|
||||||
|
for(let i=0; i<(lines.length-100); i++) lines[i].remove()
|
||||||
|
}
|
||||||
|
this.outputs.results.scrollTo({ top: this.outputs.results.scrollHeight, behavior: 'smooth' })
|
||||||
|
},
|
||||||
|
|
||||||
|
onSnippet(evt){
|
||||||
|
const snippetName = evt.target.dataset.snippet
|
||||||
|
const codeDiv = this.outputs.results.querySelector(`div.snippet[data-snippet="${snippetName}"]`)
|
||||||
|
if(codeDiv){ this.outputs.commands.value = codeDiv.textContent }
|
||||||
|
},
|
||||||
|
|
||||||
|
async evalCmd(code){
|
||||||
|
const api = {
|
||||||
|
log: (...args) => {
|
||||||
|
const res=[]
|
||||||
|
for(const arg of args){
|
||||||
|
if(typeof(arg)=='string') res.push(arg)
|
||||||
|
else res.push(JSON.stringify(arg))
|
||||||
|
}
|
||||||
|
return(res)
|
||||||
|
},
|
||||||
|
createAgent: async (type, properties) => {
|
||||||
|
if(Array.from(this.outputs.agentsSelector.options).find(item => item.value==type)){
|
||||||
|
const defaultValues = await this.models.agents.getDefaultProps(type)
|
||||||
|
console.log('==deflt===>', defaultValues,)
|
||||||
|
return(await this.newAgent(type, { ...defaultValues, ...properties })) //TODO: deepMerge
|
||||||
|
} else {
|
||||||
|
throw(`Invalid agent type: ${type}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeAgent: (aid) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
updateAgent: (aid, properties) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
listagentTypes: () => {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const fn = new Function(...Object.keys(api), `
|
||||||
|
return(
|
||||||
|
(async () => {
|
||||||
|
const logs = []
|
||||||
|
const log = (...args) => {
|
||||||
|
for(const arg of args){
|
||||||
|
if(typeof(arg)=='string') logs.push(arg)
|
||||||
|
else logs.push(JSON.stringify(arg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${code}
|
||||||
|
return(logs)
|
||||||
|
})()
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
|
const res = await fn(...Object.values(api))
|
||||||
|
return(
|
||||||
|
'<div class="line">'+
|
||||||
|
res.map(item => {
|
||||||
|
if(typeof(item) == 'object') return(JSON.stringify(item))
|
||||||
|
return(item)
|
||||||
|
}).join('</div><div class="line">')
|
||||||
|
+'</div>'
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
return(`<div class="error">${err.name}: ${err.message}</div>`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ class AgentsModel extends WindozModel {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.ressource = '/agents'
|
this.ressource = '/agents'
|
||||||
|
this.agentProps = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTypes(family) {
|
async getTypes(family) {
|
||||||
@@ -23,14 +24,28 @@ class AgentsModel extends WindozModel {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProperties(id) {
|
async getProperties(id, force=false) {
|
||||||
let endpoint = {...app.config.api[this.ressource].getProperties}
|
if((!(id in this.agentProps)) || force){
|
||||||
endpoint.uri = endpoint.uri.replace('{id}', id)
|
let endpoint = {...app.config.api[this.ressource].getProperties}
|
||||||
return (
|
endpoint.uri = endpoint.uri.replace('{id}', id)
|
||||||
this.request(endpoint.uri, endpoint.method)
|
return (
|
||||||
.then( async serverData => serverData.payload.agentProperties)
|
this.request(endpoint.uri, endpoint.method)
|
||||||
)
|
.then( async serverData => {
|
||||||
}
|
this.agentProps[id] = serverData.payload.agentProperties.atp_props
|
||||||
|
return(serverData.payload.agentProperties.atp_props)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return(this.agentProps[id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDefaultProps(id){
|
||||||
|
const aprops = await this.getProperties(id)
|
||||||
|
const defaults={ position: { x:0, y:0, z:0 }, speed: { x:0, y:0, z:0 }}
|
||||||
|
for(const p in aprops) defaults[p] = aprops[p].default
|
||||||
|
return(defaults)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.registerClass('AgentsModel', AgentsModel);
|
app.registerClass('AgentsModel', AgentsModel);
|
||||||
@@ -634,6 +634,12 @@ body[eicapp] {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 0 25px rgb(255, 255, 255);
|
box-shadow: 0 0 25px rgb(255, 255, 255);
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
|
transform: translateX(150%);
|
||||||
|
animation: growlerSlideIn 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
@keyframes growlerSlideIn {
|
||||||
|
from { transform: translateX(150%); }
|
||||||
|
to { transform: translateX(0); }
|
||||||
}
|
}
|
||||||
[eicapp] .eic-growler [eicalert][danger],
|
[eicapp] .eic-growler [eicalert][danger],
|
||||||
[eicapp] .eic-growler [eicalert][danger]:after,
|
[eicapp] .eic-growler [eicalert][danger]:after,
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ class KeyframeView extends WindozDomContent {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
Object.assign(this, app.helpers.activeAttributes)
|
Object.assign(this, app.helpers.activeAttributes, app.helpers.formBuilder, app.helpers.kfConsole)
|
||||||
Object.assign(this, app.helpers.formBuilder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DOMContentFocused(options) {
|
DOMContentFocused(options) {
|
||||||
@@ -65,67 +64,6 @@ class KeyframeView extends WindozDomContent {
|
|||||||
this.currentlySelectedAid = null
|
this.currentlySelectedAid = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async execCommand(event){
|
|
||||||
console.log('cmd:', this.outputs.commands)
|
|
||||||
if(this.outputs.commands.value.trim()=='\\help'){
|
|
||||||
this.outputs.results.innerHTML += await app.Assets.loadHtml({ name: 'help/KFconsoleHelp.html' })
|
|
||||||
} else if(this.outputs.commands.value.trim()=='\\clear'){
|
|
||||||
this.outputs.results.innerHTML = ''
|
|
||||||
} else {
|
|
||||||
this.outputs.results.innerHTML += await this.evalCmd(this.outputs.commands.value)
|
|
||||||
}
|
|
||||||
const lines = this.outputs.results.querySelectorAll('div.line')
|
|
||||||
if(lines.length > 100) {
|
|
||||||
for(let i=0; i<(lines.length-100); i++) lines[i].remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async evalCmd(code){
|
|
||||||
const api = {
|
|
||||||
newAgent: async (type, properties) => {
|
|
||||||
if(Array.from(this.outputs.agentsSelector.options).find(item => item.value==type)){
|
|
||||||
this.outputs.agentsSelector.value = type
|
|
||||||
await this.onChangeAgent()
|
|
||||||
const defaultValues = this.getFieldsValues('div[data-output="agentProperties"]')
|
|
||||||
return(await this.newAgent(type, { ...defaultValues, ...properties })) //TODO: deepMerge
|
|
||||||
} else {
|
|
||||||
throw(`Invalid agent type: ${type}`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeAgent: (aid) => {
|
|
||||||
|
|
||||||
},
|
|
||||||
updateAgent: (aid, properties) => {
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const fn = new Function(...Object.keys(api), `
|
|
||||||
return(
|
|
||||||
(async () => {
|
|
||||||
const logs = []
|
|
||||||
const log = (item) => { logs.push(item) }
|
|
||||||
${code}
|
|
||||||
return(logs)
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
|
|
||||||
const res = await fn(...Object.values(api))
|
|
||||||
return(
|
|
||||||
'<div class="line">'+
|
|
||||||
res.map(item => {
|
|
||||||
if(typeof(item) == 'object') return(JSON.stringify(item))
|
|
||||||
return(item)
|
|
||||||
}).join('</div><div class="line">')
|
|
||||||
+'</div>'
|
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
return(`<div class="error">${err.name}: ${err.message}</div>`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onChangeAgent(event){
|
async onChangeAgent(event){
|
||||||
if(this.outputs.agentsSelector.value) this.agentPreview.setAgent(this.outputs.agentsSelector.value)
|
if(this.outputs.agentsSelector.value) this.agentPreview.setAgent(this.outputs.agentsSelector.value)
|
||||||
if(!this.outputs.agentsSelector.value) return
|
if(!this.outputs.agentsSelector.value) return
|
||||||
@@ -134,7 +72,7 @@ class KeyframeView extends WindozDomContent {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.currentAgentType = await this.models.agents.getProperties(this.outputs.agentsSelector.value)
|
this.currentAgentType = await this.models.agents.getProperties(this.outputs.agentsSelector.value)
|
||||||
this.fillAgentProperties('', this.currentAgentType.atp_props)
|
this.fillAgentProperties('', this.currentAgentType)
|
||||||
// Deselect any on-scene selection
|
// Deselect any on-scene selection
|
||||||
if(this.currentlySelectedAid){
|
if(this.currentlySelectedAid){
|
||||||
this.kfArena.clearHighlight3DObj(this.kfArena.scene.getObjectByName(this.currentlySelectedAid), this.kfArena.scene)
|
this.kfArena.clearHighlight3DObj(this.kfArena.scene.getObjectByName(this.currentlySelectedAid), this.kfArena.scene)
|
||||||
@@ -191,9 +129,10 @@ class KeyframeView extends WindozDomContent {
|
|||||||
this.kfArena.removeAgent(this.currentlySelectedAid)
|
this.kfArena.removeAgent(this.currentlySelectedAid)
|
||||||
}
|
}
|
||||||
|
|
||||||
newAgent(aType, AgentValues){
|
async newAgent(aType, AgentValues){
|
||||||
const aid = crypto.randomUUIDv7()
|
const aid = crypto.randomUUIDv7()
|
||||||
this.kfArena.addAgent(aType, aid, this.currentAgentType.atp_props , AgentValues)
|
const agentProps = await this.models.agents.getProperties(aType)
|
||||||
|
this.kfArena.addAgent(aType, aid, agentProps, AgentValues)
|
||||||
return(aid)
|
return(aid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,3 +230,19 @@ class KeyframeView extends WindozDomContent {
|
|||||||
|
|
||||||
app.registerClass('KeyframeView', KeyframeView)
|
app.registerClass('KeyframeView', KeyframeView)
|
||||||
|
|
||||||
|
//TODO :
|
||||||
|
/*
|
||||||
|
|
||||||
|
API update
|
||||||
|
API remove
|
||||||
|
API listAgentTypes
|
||||||
|
if unsavec changes in scene => confirm before reloading
|
||||||
|
|
||||||
|
|
||||||
|
Bugs
|
||||||
|
|
||||||
|
=> reselect same scene resets it
|
||||||
|
=> Added with API = non-selectable
|
||||||
|
=> loaded from KF => loosing internal props
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
||||||
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'
|
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
|
|
||||||
export class kfArena{
|
export class kfArena{
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ export class kfArena{
|
|||||||
initScene(){
|
initScene(){
|
||||||
// Scene
|
// Scene
|
||||||
this.scene = new THREE.Scene()
|
this.scene = new THREE.Scene()
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
this.camera = new THREE.PerspectiveCamera(75, this.canvasEl.clientWidth / this.canvasEl.clientHeight, 0.1, 1000)
|
this.camera = new THREE.PerspectiveCamera(75, this.canvasEl.clientWidth / this.canvasEl.clientHeight, 0.1, 1000)
|
||||||
this.camera.position.set(3, 3, 5)
|
this.camera.position.set(3, 3, 5)
|
||||||
@@ -30,11 +31,16 @@ export class kfArena{
|
|||||||
this.camera.layers.enable(2)
|
this.camera.layers.enable(2)
|
||||||
|
|
||||||
// Lights
|
// Lights
|
||||||
const light = new THREE.DirectionalLight(0xffffff, 1)
|
const dLight1 = new THREE.DirectionalLight(0xffffff, 1.5)
|
||||||
light.position.set(5, 5, 5)
|
dLight1.position.set(5, 5, 5)
|
||||||
this.scene.add(light)
|
this.scene.add(dLight1)
|
||||||
this.scene.add(new THREE.AmbientLight(0xffffff, .4))
|
const dLight2 = new THREE.DirectionalLight(0xffffff, 1.5)
|
||||||
|
dLight2.position.set(-5, -5, 5)
|
||||||
|
this.scene.add(dLight2)
|
||||||
|
|
||||||
|
this.scene.add(new THREE.AmbientLight(0xffffff, 1))
|
||||||
|
|
||||||
|
|
||||||
this.grid = new THREE.GridHelper(this.sceneSize.x, this.sceneSize.x, 0x8888AA, 0x8888AA)
|
this.grid = new THREE.GridHelper(this.sceneSize.x, this.sceneSize.x, 0x8888AA, 0x8888AA)
|
||||||
this.grid.layers.set(1)
|
this.grid.layers.set(1)
|
||||||
this.scene.add(this.grid)
|
this.scene.add(this.grid)
|
||||||
@@ -57,10 +63,6 @@ export class kfArena{
|
|||||||
this.scene.add(this.axes)
|
this.scene.add(this.axes)
|
||||||
|
|
||||||
this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvasEl, stencil: true })
|
this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvasEl, stencil: true })
|
||||||
// this.renderer.physicallyCorrectLights = true
|
|
||||||
// this.renderer.outputColorSpace = THREE.SRGBColorSpace
|
|
||||||
// this.renderer.toneMapping = THREE.ACESFilmicToneMapping
|
|
||||||
// this.renderer.toneMappingExposure = 1
|
|
||||||
|
|
||||||
this.canvasEl.addEventListener('click', this.onSceneClick.bind(this))
|
this.canvasEl.addEventListener('click', this.onSceneClick.bind(this))
|
||||||
}
|
}
|
||||||
@@ -74,6 +76,7 @@ export class kfArena{
|
|||||||
TWEEN.update()
|
TWEEN.update()
|
||||||
this.animateHighlight3DObj()
|
this.animateHighlight3DObj()
|
||||||
const resized = this.resizeRendererToDisplaySize()
|
const resized = this.resizeRendererToDisplaySize()
|
||||||
|
|
||||||
this.renderer.render(this.scene, this.camera)
|
this.renderer.render(this.scene, this.camera)
|
||||||
requestAnimationFrame(this.render.bind(this))
|
requestAnimationFrame(this.render.bind(this))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,9 @@
|
|||||||
"imports": {
|
"imports": {
|
||||||
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
||||||
"three/examples/jsm/loaders/FontLoader.js": "https://unpkg.com/three@0.160.0/examples/jsm/loaders/FontLoader.js",
|
"three/examples/jsm/loaders/FontLoader.js": "https://unpkg.com/three@0.160.0/examples/jsm/loaders/FontLoader.js",
|
||||||
"three/examples/jsm/geometries/TextGeometry.js": "https://unpkg.com/three@0.160.0/examples/jsm/geometries/TextGeometry.js",
|
"three/examples/jsm/loaders/GLTFLoader.js": "https://unpkg.com/three@0.160.0/examples/jsm/loaders/GLTFLoader.js",
|
||||||
"three/examples/jsm/loaders/RGBELoader.js": "https://unpkg.com/three@0.160.0/examples/jsm/loaders/RGBELoader.js",
|
"three/examples/jsm/loaders/RGBELoader.js": "https://unpkg.com/three@0.160.0/examples/jsm/loaders/RGBELoader.js",
|
||||||
|
"three/examples/jsm/geometries/TextGeometry.js": "https://unpkg.com/three@0.160.0/examples/jsm/geometries/TextGeometry.js",
|
||||||
"three/examples/jsm/controls/OrbitControls.js": "https://unpkg.com/three@0.160.0/examples/jsm/controls/OrbitControls.js",
|
"three/examples/jsm/controls/OrbitControls.js": "https://unpkg.com/three@0.160.0/examples/jsm/controls/OrbitControls.js",
|
||||||
"three/examples/jsm/libs/tween.module.js": "https://unpkg.com/three@0.160.0/examples/jsm/libs/tween.module.js"
|
"three/examples/jsm/libs/tween.module.js": "https://unpkg.com/three@0.160.0/examples/jsm/libs/tween.module.js"
|
||||||
}
|
}
|
||||||
|
|||||||