Bläddra i källkod

NodeMaterial: Playground - WebGL (#22833)

* tabler-icons - MIT License

* open-sans optional font

* add flow ui

* node-editor playground ( webgl example )

* add webgl_materials_nodes_playground metas

* add Normal, Position, UV and Checker editor

* cleanup

* add colors

* update flow direction -> left-to-right

* update thumbnail

* fix OperatorEditor inputs
sunag 3 år sedan
förälder
incheckning
607c5c0804

+ 1 - 0
examples/files.json

@@ -230,6 +230,7 @@
 		"webgl_materials_envmaps_pmrem_nodes",
 		"webgl_materials_instance_uniform_nodes",
 		"webgl_materials_nodes",
+		"webgl_materials_nodes_playground",
 		"webgl_materials_standard_nodes",
 		"webgl_mirror_nodes",
 		"webgl_performance_nodes",

BIN
examples/fonts/open-sans/open-sans-v15-cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese-regular.woff


BIN
examples/fonts/open-sans/open-sans-v15-cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese-regular.woff2


+ 9 - 0
examples/fonts/open-sans/open-sans.css

@@ -0,0 +1,9 @@
+/* open-sans-regular - cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'),
+       url('./open-sans-v15-cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+       url('./open-sans-v15-cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}

BIN
examples/fonts/tabler-icons/fonts/tabler-icons.eot


BIN
examples/fonts/tabler-icons/fonts/tabler-icons.ttf


BIN
examples/fonts/tabler-icons/fonts/tabler-icons.woff


BIN
examples/fonts/tabler-icons/fonts/tabler-icons.woff2


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 3 - 0
examples/fonts/tabler-icons/tabler-icons.min.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 18 - 0
examples/jsm/libs/flow.module.js


+ 246 - 0
examples/jsm/node-editor/NodeEditor.js

@@ -0,0 +1,246 @@
+import { Canvas, CircleMenu, ButtonInput, ContextMenu, Loader } from '../libs/flow.module.js';
+import { StandardMaterialEditor } from './materials/StandardMaterialEditor.js';
+import { OperatorEditor } from './math/OperatorEditor.js';
+import { FloatEditor } from './inputs/FloatEditor.js';
+import { ColorEditor } from './inputs/ColorEditor.js';
+import { UVEditor } from './accessors/UVEditor.js';
+import { PositionEditor } from './accessors/PositionEditor.js';
+import { NormalEditor } from './accessors/NormalEditor.js';
+import { CheckerEditor } from './procedural/CheckerEditor.js';
+import { EventDispatcher } from 'three';
+
+export const ClassLib = {
+	'StandardMaterialEditor': StandardMaterialEditor,
+	'OperatorEditor': OperatorEditor,
+	'FloatEditor': FloatEditor,
+	'ColorEditor': ColorEditor,
+	'UVEditor': UVEditor,
+	'PositionEditor': PositionEditor,
+	'NormalEditor': NormalEditor,
+	'CheckerEditor': CheckerEditor
+};
+
+export class NodeEditor extends EventDispatcher {
+
+	constructor() {
+
+		super();
+
+		const domElement = document.createElement( 'flow' );
+		const canvas = new Canvas();
+
+		domElement.appendChild( canvas.dom );
+
+		this.canvas = canvas;
+		this.domElement = domElement;
+
+		this._initMenu();
+		this._initContextMenu();
+
+	}
+
+	add( node ) {
+
+		this.canvas.add( node );
+
+		return this;
+
+	}
+
+	get nodes() {
+
+		return this.canvas.nodes;
+
+	}
+
+	_initMenu() {
+
+		const menu = new CircleMenu();
+
+		const menuButton = new ButtonInput().setIcon( 'ti ti-menu-2' );
+		const newButton = new ButtonInput().setIcon( 'ti ti-file' ).setToolTip( 'New' );
+		const openButton = new ButtonInput().setIcon( 'ti ti-upload' ).setToolTip( 'Open' );
+		const saveButton = new ButtonInput().setIcon( 'ti ti-download' ).setToolTip( 'Save' );
+
+		menuButton.onClick( () => {
+
+			this.context.show( 50, 50 );
+
+		} );
+
+		newButton.onClick( () => {
+
+			this.canvas.clear();
+
+			this.dispatchEvent( { type: 'new' } );
+
+		} );
+
+		openButton.onClick( () => {
+
+			this.context.hide();
+
+			const input = document.createElement( 'input' );
+			input.type = 'file';
+
+			input.onchange = e => {
+
+				const file = e.target.files[ 0 ];
+
+				const reader = new FileReader();
+				reader.readAsText( file, 'UTF-8' );
+
+				reader.onload = readerEvent => {
+
+					const json = Loader.parseObjects( JSON.parse( readerEvent.target.result ), ClassLib );
+
+					this.canvas.clear();
+
+					this.canvas.deserialize( json );
+
+					this.dispatchEvent( { type: 'load' } );
+
+				};
+
+			};
+
+			input.click();
+
+		} );
+
+		saveButton.onClick( () => {
+
+			this.context.hide();
+
+			const json = JSON.stringify( this.canvas.toJSON() );
+
+			const a = document.createElement( 'a' );
+			const file = new Blob( [ json ], { type: 'text/plain' } );
+
+			a.href = URL.createObjectURL( file );
+			a.download = 'node_editor.json';
+			a.click();
+
+		} );
+
+		menu.add( menuButton );
+		menu.add( newButton );
+		menu.add( openButton );
+		menu.add( saveButton );
+
+		this.domElement.appendChild( menu.dom );
+
+		this.menu = menu;
+
+	}
+
+	_initContextMenu() {
+
+		const context = new ContextMenu( this.domElement );
+
+		const add = ( node ) => {
+
+			const canvas = this.canvas;
+			const canvasRect = canvas.rect;
+
+			node.setPosition(
+				( canvas.relativeX + ( canvasRect.width / 2 ) ) - ( 350 / 2 ),
+				( canvas.relativeY + ( canvasRect.height / 2 ) ) - 20
+			);
+
+			context.hide();
+
+			this.add( node );
+
+			this.canvas.select( node );
+
+		};
+
+		//**************//
+		//* INPUTS
+		//**************//
+
+		const inputsContext = new ContextMenu();
+
+		const floatInput = new ButtonInput( 'Float' ).setIcon( 'ti ti-box-multiple-1' )
+			.onClick( () => add( new FloatEditor() ) );
+
+		//const vec2Input = new ButtonInput( 'Vector 2' ).setIcon( 'ti ti-box-multiple-2' );
+		//const vec3Input = new ButtonInput( 'Vector 3' ).setIcon( 'ti ti-box-multiple-3' );
+		//const vec4Input = new ButtonInput( 'Vector 4' ).setIcon( 'ti ti-box-multiple-4' );
+
+		const colorInput = new ButtonInput( 'Color' ).setIcon( 'ti ti-palette' )
+			.onClick( () => add( new ColorEditor() ) );
+
+		//const mapInput = new ButtonInput( 'Map' ).setIcon( 'ti ti-photo' );
+		//const cubeMapInput = new ButtonInput( 'Cube Map' ).setIcon( 'ti ti-box' );
+		//const sliderInput = new ButtonInput( 'Slider' ).setIcon( 'ti ti-adjustments-horizontal' );
+		//const integerInput = new ButtonInput( 'Integer' ).setIcon( 'ti ti-list-numbers' );
+
+		inputsContext
+			.add( floatInput )
+			//.add( vec2Input )
+			//.add( vec3Input )
+			//.add( vec4Input )
+			.add( colorInput );
+		//.add( sliderInput );
+
+		//**************//
+		//* MATH
+		//**************//
+
+		const mathContext = new ContextMenu();
+		const operatorsNode = new ButtonInput( 'Operators' ).setIcon( 'ti ti-math-symbols' )
+			.onClick( () => add( new OperatorEditor() ) );
+
+		mathContext
+			.add( operatorsNode );
+
+		//**************//
+		//* ACCESSORS
+		//**************//
+
+		const accessorsContext = new ContextMenu();
+
+		const uvNode = new ButtonInput( 'UV' ).setIcon( 'ti ti-details' )
+			.onClick( () => add( new UVEditor() ) );
+
+		const positionNode = new ButtonInput( 'Position' ).setIcon( 'ti ti-hierarchy' )
+			.onClick( () => add( new PositionEditor() ) );
+
+		const normalNode = new ButtonInput( 'Normal' ).setIcon( 'ti ti-fold-up' )
+			.onClick( () => add( new NormalEditor() ) );
+
+		accessorsContext
+			.add( uvNode )
+			.add( positionNode )
+			.add( normalNode );
+
+		//**************//
+		//* PROCEDURAL
+		//**************//
+
+		const proceduralContext = new ContextMenu();
+
+		const checkerNode = new ButtonInput( 'Checker' ).setIcon( 'ti ti-border-outer' )
+			.onClick( () => add( new CheckerEditor() ) );
+
+		proceduralContext
+			.add( checkerNode );
+
+		//**************//
+		//* MAIN
+		//**************//
+
+		context.add( new ButtonInput( 'Inputs' ).setIcon( 'ti ti-forms' ), inputsContext );
+		context.add( new ButtonInput( 'Accessors' ).setIcon( 'ti ti-vector-triangle' ), accessorsContext );
+		context.add( new ButtonInput( 'Math' ).setIcon( 'ti ti-calculator' ), mathContext );
+		context.add( new ButtonInput( 'Procedural' ).setIcon( 'ti ti-infinity' ), proceduralContext );
+
+		this.domElement.appendChild( context.dom );
+
+		this.context = context;
+
+	}
+
+}

+ 31 - 0
examples/jsm/node-editor/accessors/NormalEditor.js

@@ -0,0 +1,31 @@
+import { ObjectNode } from '../core/ObjectNode.js';
+import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { NormalNode } from '../../renderers/nodes/Nodes.js';
+
+export class NormalEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new NormalNode();
+
+		super( 'Normal', 3, node );
+
+		this.title.setStyle( 'red' );
+
+		const optionsField = new SelectInput( [
+			{ name: 'Local', value: NormalNode.LOCAL },
+			{ name: 'World', value: NormalNode.WORLD },
+			{ name: 'View', value: NormalNode.VIEW }
+		] ).onChange( () => {
+
+			node.scope = optionsField.getValue();
+
+			this.invalidate();
+
+		} );
+
+		this.add( new LabelElement( 'Scope' ).add( optionsField ) );
+
+	}
+
+}

+ 31 - 0
examples/jsm/node-editor/accessors/PositionEditor.js

@@ -0,0 +1,31 @@
+import { ObjectNode } from '../core/ObjectNode.js';
+import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { PositionNode } from '../../renderers/nodes/Nodes.js';
+
+export class PositionEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new PositionNode();
+
+		super( 'Position', 3, node );
+
+		this.title.setStyle( 'red' );
+
+		const optionsField = new SelectInput( [
+			{ name: 'Local', value: PositionNode.LOCAL },
+			{ name: 'World', value: PositionNode.WORLD },
+			{ name: 'View', value: PositionNode.VIEW }
+		] ).onChange( () => {
+
+			node.scope = optionsField.getValue();
+
+			this.invalidate();
+
+		} );
+
+		this.add( new LabelElement( 'Scope' ).add( optionsField ) );
+
+	}
+
+}

+ 27 - 0
examples/jsm/node-editor/accessors/UVEditor.js

@@ -0,0 +1,27 @@
+import { ObjectNode } from '../core/ObjectNode.js';
+import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { UVNode } from '../../renderers/nodes/Nodes.js';
+
+export class UVEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new UVNode();
+
+		super( 'UV', 2, node );
+
+		this.title.setStyle( 'red' );
+
+		const optionsField = new SelectInput( [ '1', '2' ] ).onChange( () => {
+
+			node.value = Number( optionsField.getValue() );
+
+			this.invalidate();
+
+		} );
+
+		this.add( new LabelElement( 'Channel' ).add( optionsField ) );
+
+	}
+
+}

+ 34 - 0
examples/jsm/node-editor/core/ObjectNode.js

@@ -0,0 +1,34 @@
+import { Node, TitleElement, ButtonInput } from '../../libs/flow.module.js';
+
+export class ObjectNode extends Node {
+
+	constructor( name, inputLength, extra = null ) {
+
+		super();
+
+		const title = new TitleElement( name )
+			.setExtra( extra )
+			.setOutput( inputLength );
+
+		const closeButton = new ButtonInput( '✖' ).onClick( () => {
+
+			this.dispose();
+
+		} );
+
+		title.addButton( closeButton );
+
+		this.add( title );
+
+		this.title = title;
+		this.closeButton = closeButton;
+
+	}
+
+	invalidate() {
+
+		this.title.dispatchEvent( new Event( 'connect' ) );
+
+	}
+
+}

+ 27 - 0
examples/jsm/node-editor/inputs/ColorEditor.js

@@ -0,0 +1,27 @@
+import { ObjectNode } from '../core/ObjectNode.js';
+import { ColorInput, LabelElement } from '../../libs/flow.module.js';
+import { ColorNode } from '../../renderers/nodes/Nodes.js';
+
+export class ColorEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new ColorNode();
+
+		super( 'Color', 1, node );
+
+		this.title.setIcon( 'ti ti-palette' );
+
+		const colorField = new ColorInput( 0xFFFFFF ).onChange( ( input ) => {
+
+			const hex = parseInt( input.getValue() );
+
+			node.value.setHex( hex );
+
+		} );
+
+		this.add( new LabelElement( 'Value' ).add( colorField ) );
+
+	}
+
+}

+ 25 - 0
examples/jsm/node-editor/inputs/FloatEditor.js

@@ -0,0 +1,25 @@
+import { ObjectNode } from '../core/ObjectNode.js';
+import { NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { FloatNode } from '../../renderers/nodes/Nodes.js';
+
+export class FloatEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new FloatNode( 0 );
+
+		super( 'Float', 1, node );
+
+		this.title.setIcon( 'ti ti-box-multiple-1' );
+
+		const numberField = new NumberInput().onChange( ( input ) => {
+
+			node.value = input.getValue();
+
+		} );
+
+		this.add( new LabelElement( 'Value' ).add( numberField ) );
+
+	}
+
+}

+ 70 - 0
examples/jsm/node-editor/materials/StandardMaterialEditor.js

@@ -0,0 +1,70 @@
+import { ObjectNode } from '../core/ObjectNode.js';
+import { LabelElement } from '../../libs/flow.module.js';
+import { MeshStandardNodeMaterial, ColorNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+import * as THREE from 'three';
+
+const NULL_COLOR = new ColorNode();
+const NULL_FLOAT = new FloatNode();
+
+export class StandardMaterialEditor extends ObjectNode {
+
+	constructor() {
+
+		const material = new MeshStandardNodeMaterial();
+
+		super( 'Standard Material', 0, material );
+
+		this.title.setStyle( 'blue' );
+
+		this.setWidth( 300 );
+
+		const color = new LabelElement( 'color' ).setInput( 3 );
+		const opacity = new LabelElement( 'opacity' ).setInput( 1 );
+		const metalness = new LabelElement( 'metalness' ).setInput( 1 );
+		const roughness = new LabelElement( 'roughness' ).setInput( 1 );
+
+		color.onConnect( () => this.update(), true );
+		opacity.onConnect( () => this.update(), true );
+		metalness.onConnect( () => this.update(), true );
+		roughness.onConnect( () => this.update(), true );
+
+		this.add( color )
+			.add( opacity )
+			.add( metalness )
+			.add( roughness );
+
+		this.color = color;
+		this.opacity = opacity;
+		this.metalness = metalness;
+		this.roughness = roughness;
+
+		this.material = material;
+
+		this.update();
+
+	}
+
+	update() {
+
+		const { material, color, opacity, roughness, metalness } = this;
+
+		material.colorNode = color.linkedExtra || NULL_COLOR;
+
+		material.opacityNode = opacity.linkedExtra || null;
+		material.transparent = opacity.linkedExtra ? true : false;
+
+		material.metalnessNode = metalness.linkedExtra || NULL_FLOAT;
+		material.roughnessNode = roughness.linkedExtra || NULL_FLOAT;
+
+		material.dispose();
+
+		// TODO: Fix on NodeMaterial System
+		material.customProgramCacheKey = () => {
+
+			return THREE.MathUtils.generateUUID();
+
+		};
+
+	}
+
+}

+ 53 - 0
examples/jsm/node-editor/math/OperatorEditor.js

@@ -0,0 +1,53 @@
+import { ObjectNode } from '../core/ObjectNode.js';
+import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { OperatorNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const NULL_VALUE = new FloatNode();
+
+export class OperatorEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new OperatorNode( '+', NULL_VALUE, NULL_VALUE );
+
+		super( 'Float', 1, node );
+
+		this.title.setStyle( 'green' );
+
+		const opInput = new SelectInput( [
+			{ name: '+ Addition', value: '+' },
+			{ name: '- Subtraction', value: '-' },
+			{ name: '* Multiplication', value: '*' },
+			{ name: '/ Division', value: '/' }
+		] );
+
+		opInput.onChange( ( data ) => {
+
+			node.op = data.getValue();
+
+			this.invalidate();
+
+		} );
+
+		const aElement = new LabelElement( 'A' ).setInput( 3 );
+		const bElement = new LabelElement( 'B' ).setInput( 3 );
+
+		aElement.onConnect( () => {
+
+			node.aNode = aElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		bElement.onConnect( () => {
+
+			node.bNode = bElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		this.add( new LabelElement( 'Operator' ).add( opInput ) )
+			.add( aElement )
+			.add( bElement );
+
+	}
+
+}

+ 29 - 0
examples/jsm/node-editor/procedural/CheckerEditor.js

@@ -0,0 +1,29 @@
+import { ObjectNode } from '../core/ObjectNode.js';
+import { LabelElement } from '../../libs/flow.module.js';
+import { CheckerNode, UVNode } from '../../renderers/nodes/Nodes.js';
+
+const DEFAULT_UV = new UVNode();
+
+export class CheckerEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new CheckerNode();
+
+		super( 'Checker', 1, node );
+
+		this.title.setStyle( 'yellow' );
+
+		const field = new LabelElement( 'UV' ).setInput( 2 );
+
+		field.onConnect( () => {
+
+			node.uvNode = field.linkedExtra || DEFAULT_UV;
+
+		} );
+
+		this.add( field );
+
+	}
+
+}

BIN
examples/screenshots/webgl_materials_nodes_playground.jpg


+ 190 - 0
examples/webgl_materials_nodes_playground.html

@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+		<title>three.js webgl - node-editor playground</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link rel="stylesheet" href="fonts/open-sans/open-sans.css" type="text/css"/>
+		<link rel="stylesheet" href="fonts/tabler-icons/tabler-icons.min.css" type="text/css"/>
+		<link type="text/css" rel="stylesheet" href="main.css">
+  </head>
+  <body>
+	<script type="importmap">
+	{
+		"imports": {
+			"three": "../build/three.module.js"
+		}
+	}
+	</script>
+	<style>
+	body {
+		overflow: hidden;
+		width: 100%;
+		height: 100%;
+	}
+	.renderer {
+		position: absolute;
+		top: 0;
+		left: 0;
+		height: 50%;
+		width: 100%;
+	}
+	flow {
+		position: absolute;
+		top: 50%;
+		left: 0;
+		height: 50%;
+		width: 100%;
+		background: #222;
+		box-shadow: inset 0 0 20px 0px #000000;
+	}
+	</style>
+	<script type="module">
+
+		import * as THREE from 'three';
+
+		import { nodeFrame } from './jsm/renderers/webgl/nodes/WebGLNodes.js';
+
+		import { NodeEditor } from './jsm/node-editor/NodeEditor.js';
+		import { StandardMaterialEditor } from './jsm/node-editor/materials/StandardMaterialEditor.js';
+
+		import Stats from './jsm/libs/stats.module.js';
+
+		import { OrbitControls } from './jsm/controls/OrbitControls.js';
+		import { FBXLoader } from './jsm/loaders/FBXLoader.js';
+
+		let stats;
+		let camera, scene, renderer;
+		let model;
+
+		init();
+		animate();
+
+		function init() {
+
+			camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 5000 );
+			camera.position.set( 0.0, 300, 400 * 3 );
+
+			scene = new THREE.Scene();
+			scene.background = new THREE.Color( 0x333333 );
+
+			// Lights
+
+			//scene.add( new THREE.AmbientLight( 0x111111 ) );
+
+			const directionalLight = new THREE.DirectionalLight( 0xF4F6F0, 0.8 );
+			directionalLight.position.set( 0.0, 0.5, 0.5 ).normalize();
+			scene.add( directionalLight );
+
+			const pointLight = new THREE.Mesh( new THREE.SphereGeometry( 4, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0x0c1445 } ) );
+			pointLight.add( new THREE.PointLight( 0x0c1445, 0.7, 500 ) );
+			scene.add( pointLight );
+			pointLight.position.x = - 100;
+			pointLight.position.y = 20;
+			pointLight.position.z = - 260;
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			document.body.appendChild( renderer.domElement );
+			renderer.outputEncoding = THREE.sRGBEncoding;
+
+			renderer.domElement.className = 'renderer';
+
+			//
+
+			stats = new Stats();
+			document.body.appendChild( stats.dom );
+
+			const controls = new OrbitControls( camera, renderer.domElement );
+			controls.minDistance = 500;
+			controls.maxDistance = 3000;
+
+			window.addEventListener( 'resize', onWindowResize );
+
+			onWindowResize();
+
+			initEditor();
+
+		}
+
+		function initEditor() {
+
+			const nodeEditor = new NodeEditor();
+
+			nodeEditor.addEventListener( 'new', () => {
+
+				const materialEditor = new StandardMaterialEditor();
+				materialEditor.setPosition( ( window.innerWidth / 2 ) - 150, 100 );
+
+				nodeEditor.add( materialEditor );
+
+				model.material = materialEditor.material;
+
+			} );
+
+			nodeEditor.addEventListener( 'load', () => {
+
+				const materialEditor = nodeEditor.nodes[ 0 ];
+				materialEditor.update(); // need move to deserialization
+
+				model.material = materialEditor.material;
+
+			} );
+
+			document.body.appendChild( nodeEditor.domElement );
+
+			const loaderFBX = new FBXLoader();
+			loaderFBX.load( 'models/fbx/stanford-bunny.fbx', ( object ) => {
+
+				const materialEditor = new StandardMaterialEditor();
+				materialEditor.setPosition( ( window.innerWidth / 2 ) - 150, 100 ); // canvas position
+
+				nodeEditor.add( materialEditor );
+
+				model = object.children[ 0 ];
+				model.position.set( 0, 0, 10 );
+				model.scale.setScalar( 1 );
+				model.material = materialEditor.material;
+				scene.add( model );
+
+			} );
+
+		}
+
+		function onWindowResize() {
+
+			const width = window.innerWidth;
+			const height = window.innerHeight / 2;
+
+			camera.aspect = width / height;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize( width, height );
+
+		}
+
+		//
+
+		function animate() {
+
+			requestAnimationFrame( animate );
+
+			nodeFrame.update();
+
+			render();
+
+			stats.update();
+
+		}
+
+		function render() {
+
+			//if ( model ) model.rotation.y = performance.now() / 5000;
+
+			renderer.render( scene, camera );
+
+		}
+
+	</script>
+
+  </body>
+</html>

Vissa filer visades inte eftersom för många filer har ändrats