Coder Social home page Coder Social logo

hmans / three-elements Goto Github PK

View Code? Open in Web Editor NEW
397.0 10.0 14.0 5.59 MB

Web Components-powered custom HTML elements for building Three.js-powered games and interactive experiences. ๐ŸŽ‰

Home Page: https://three-elements.hmans.co

License: MIT License

TypeScript 55.34% JavaScript 6.11% Vue 0.91% Stylus 0.14% HTML 37.17% CSS 0.33%
html-elements web-components custom-elements threejs webgl 3d-scene dom

three-elements's Introduction

New Maintainer Wanted!

I am no longer working on this library (since I am also no longer using it, unfortunately.) If you're interested in taking over, please contact me at [email protected]. Thanks!




Version Version CI Downloads Bundle Size Discord

 __   __    ๐Ÿ—ป     ๐Ÿ—ป   โ›ฐ
|  |_|  |--.----.-----.-----.          ๐Ÿฆ…                                      ๐ŸŒž
|   _|     |   _|  -__|  -__|
|____|__|__|__| |_____|_____|  ๐Ÿ—ป  ๐ŸŒฒ๐ŸŒณ      __  ๐ŸŒฒ  ๐ŸŒณ                ๐Ÿฆ…
    .-----.|  |.-----.--------.-----.-----.|  |_.-----.
    |  -__||  ||  -__|        |  -__|     ||   _|__ --|
    |_____||__||_____|__|__|__|_____|__|__||____|_____|   ๐ŸŒณ๐ŸŒฒ ๐Ÿก ๐ŸŒฒ   ๐ŸŒฒ๐ŸŒณ  ๐Ÿ„   ๐ŸŒฒ ๐ŸŒฒ๐ŸŒณ     ๐ŸŒณ

three-elements provides Web Components-powered custom HTML elements for building Three.js-powered games and interactive experiences. ๐ŸŽ‰

WARNING: It is early days for this library, so please proceed with caution!

  • Directly exposes all Three.js classes as HTML elements (eg. <three-mesh> for THREE.Mesh!)
  • Elements are fully reactive; if their attributes change, this is immediately reflected in the Three.js scene.
  • Optimized rendering: Frames are only rendered when something has changed in the scene, or if your code explicitly requests it.
  • Input event handling: Your 3D scene automatically handles pointer events (clicks, hover, etc). Just hook into the same HTML DOM events you would use in any other web application (onclick et al.)
  • Use it with any framework that emits or modifies HTML DOM, or no framework at all!
  • Works with any version of Three.js, including your own fork if you have one.
  • Built-in templating support lets you reuse objects or entire scenes across your project without the need for any JavaScript component framework.
<!-- Load three-elements -->
<script type="module" src="https://jspm.dev/three-elements"></script>

<!-- Create a Three.js game with a default camera. -->
<three-game autorender>
  <three-scene background-color="#444">
    <!-- Lights on! -->
    <three-ambient-light intensity="0.2"></three-ambient-light>
    <three-directional-light intensity="0.8" position="10, 10, 50"></three-directional-light>

    <!-- Spinning dodecahedron! -->
    <three-mesh tick="object.rotation.z += dt">
      <three-dodecahedron-buffer-geometry></three-dodecahedron-buffer-geometry>
      <three-mesh-standard-material color="red"></three-mesh-standard-material>
    </three-mesh>
  </three-scene>
</three-game>

DOCUMENTATION

COMMUNITY

CONTRIBUTING

Please get in touch before submitting Pull Requests (ideally, before even implementing them.) At this stage in its development, three-elements still is heavily in flux. If there is something you would like to contribute, please open an issue and describe your suggestion.

If you want to do some hacking, just run yarn dev, which will compile the package in watch mode and spawn a server on localhost:5000 that serves the contents of the examples/ directory.

THANKS

  • Three.js for being the 3D library for the web.
  • A-Frame for introducing Web Components-powered easy to use 3D and VR.
  • react-three-fiber for its smart approach of mirroring THREE.* classes 1:1 instead of building a library of custom components.

three-elements's People

Contributors

codyjasonbennett avatar dependabot[bot] avatar hmans avatar pandasekh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

three-elements's Issues

[core] When handling click events, make sure a raycast has been performed this frame

Right now, we perform raycasts for pointer events whenever the pointer is moved -- click event handlers simply use the intersections generated from the earlier pointer movement event.

This leads to bugs where, when the pointer does not move, but the scene changes in any way, clicks will use outdated intersection data.

Solution: When handling click events, check if a raycast was already performed this frame, and if it hasn't, perform one.

[proxy] Improve proxy typings

In its current version, the typings provided by the proxy package are relatively basic. They're smart enough to be able to provide a list of attributes that should be exposed per Three.js object instance, but there's a number of conversions we should be applying there for properties that can take multiple three-elements-specific inputs, like:

  • Vector3 and Euler should be string | number[]
  • Scalars should be string | number
  • etc...?
  • See Trinity for implementation

Also, further improvements include:

  • Provide information which classes are actually being exposed by the proxy

[core] Similar to `tick` `late-tick` etc., also offer `mount` and `unmount` (or similar)

We currently have a convenient way of running code in every frame, but if we want to run code when an element appears (or disappears), we currently have to drop into the imperative layer or, worse, build our own class extending ThreeElement and overloading mountedCallback and friends.

It would be nice if we could add code that is run when the element appears (and disappears) by setting a property or attribute, just like we can do with ticker events.

These new attributes could be named something like mount and unmount, or appear and disappear, or something similar.

There's an opportunity here to make them real DOM events, but I think that would be a mistake; if there's a large number of objects appearing or disappearing, it would have a significant impact on performance (and memory/GC). So, just like the ticker callbacks, these need to run through an EventEmitter, which means we'll have to add one of those guys to BaseElement.

Checklist:

  • Decide on a good name for these
  • Implement them :-)

'getThreeObjectBySelector' only works on light DOM

Hi, I was trying to use three-elements inside a LitElement-based web component with Shadow DOM enabled and ended up finding that the current way of referencing other three objects by a selector isn't compatible with Shadow DOM

From what I could see in the v0.4 code, this util is only used when fetching the camera in a scene and when applying a prop using the ref directive.

I was thinking of possible solutions, one I came up with is trying to run the querySelector from the three-game ancestor, and maybe asking users to disable shadow DOM for web components meant to be used as descendants of a three-game.

But there might be a better solution.

As for workarounds, at least for setting a custom camera you can run the following code in your connectedCallback (or firstUpdated in Lit's case):

const scene = this.shadowRoot.querySelector('three-scene');
const camera = this.shadowRoot.querySelector('three-camera');
scene.camera = camera.object;

IDEA: Retire the "attach" argument

Nested geometries and materials already auto-attach to their parent if the parent is a Mesh. In those situations where we would want to handle attaching explicitly, it might be nicer to go the opposite way and have the affected objects reference things by DOM ID/selector (see #16).

[core] Provide a way to customize which elements get created

Right now, once you import "three-elements", all of Three.js will be imported, and custom elements will be generated for all of its (constructible) members. This is fine for quick hosted examples, but makes it impossible to just selectively import parts of Three.js, or perform tree-shaking in a build pipeline that offers this.

Suggestion:

  • Move the meat of the current three-elements package to a new package @three-elements/core and make importing that package side-effect free
  • Provide a way to selectively generate elements for a list of user-provided Three.js classes
  • (Maybe use the opportunity to also allow the user to customize the naming of the tags, eg. use a t- prefix instead if a three- one)
  • Reduce the three-elements package in scope so it just imports core and performs the brute-force initialization it's been doing so far.

This way, if you just want "everything", you can keep importing from "three-elements"; if you don't, you'll work with @three-elements/core and explicitly ask it to set up elements for you, following your configuration.

[core] Support MutationObserver behind a flag

As per #34 we talked about how MutationObserver isn't necessary for the actual games but is a nice to have.

Just wanted to make sure this feature was being tracked. ๐Ÿ˜

In this comment it was suggested to have an observe toggle in three-game which would enable this functionality.

RFC: Where to go with "ontick" and friends?

three-elements' <three-game> root element implements a gameloop that you can hook into using four different ticker callbacks:

  • tick (executed every tick, no exceptions; ie. 60 times per second)
  • latetick (exactly like tick, but guaranteed to run after all tick listeners are done executing; a common game loop pattern)
  • frametick (executed only on ticks that are going to render a frame)
  • rendertick (executed while rendering a frame; you can use it to hook in your custom rendering code)

Right now, in HEAD -- which is going to turn into a 0.3.0 release soon -- there are three ways to hook into these events:

  • directly attach callbacks to the three-game element's emitter, eg. game.emitter.on("tick", () => ...)
  • set the ontick, onlatetick, onframetick or onrendertick property of any three element to a function, eg. mesh.ontick = () => ...
  • set the ontick, onlatetick, onframetick or onrendertick attribute of any three element to the string representation of a function body, eg. <three-mesh ontick="this.object.rotateZ(0.01)">

An earlier version of three-elements was using DOM events (CustomEvent instances sent though dispatchEvent), but this unfortunately very quickly ruined performance once more than just a tiny handful of elements were using this mechanism.

The current setup works well and performs nicely, but the API has some issues that I'd like to resolve:

  • The fact that you can set ontick on an element also implies that the element dispatches a "tick" event (see this Twitter thread). This is not only an expected pattern, but is also an assumption made by some frameworks (eg. HyperApp) that implement their own handling of on* attributes.
  • We like to avoid surprises, but it's certainly surprising that there is a ThreeGame.emitter, but no ThreeElement.emitter. A lot of people on Twitter have voiced the opinion that it's important to be able to attach multiple event listeners to the same event, which would currently require you to attach to the three-game element's emitter directly. (Note: I don't believe this is an important use case that we need to support; ticker callbacks are not UI events, after all.)

If anyone has any suggestions on how to move forward with this API, let's hear them!

The options I'm seeing right now are the following:

  • Leave things as they are now, at least for the time being, and live with the fact that there's going to be some surprising behavior in some frameworks (ie. in lit-html, you can do .ontick=${fn}, but you can't do @tick=${fn}. It's not great, but also not the end of the world.)
  • Rename these properties/attributes to something that doesn't imply that they're reflecting DOM events, for example pertick, or tickfn, or tickcallback, or...?
  • Go back to using real DOM events, which is only acceptable if we find a way to not completely thrash performance with them.
  • Remove element-specific ticker event listeners, have only three-game emit ticks as proper DOM Events, and require that all per-frame code hook into that and only that. A pretty radical idea that would make things completely non-surprising at the cost of API approachability.

Remove dependency to inflected/Inflector

We really just need it to convert THREE member names (eg. BoxBufferGeometry) to custom element tag names (eg. <three-box-buffer-geometry>), and this could be implemented inside our package.

Provide three-* HTML tag typings

Not 100% sure what will pick this up (VS Code in HTML projects, I guess -- but other than that?) -- let's find out!

declare global {
  interface HTMLElementTagNameMap {
    'my-element': MyElement;
  }
}

Figure out how to do better onupdate callbacks in React

three-elements works fine in React, with the single caveat that you need to work with React refs in order to register update callbacks. This isn't much of a problem, but ideally we'd want to be able to directly assign functions or references to functions to onupdate and friends from with JSX.

This currently isn't possible because React will swallow the entire attribute, which is discussed at length in this thread and this in the React repository.

Further reading:

Auto-attach based on name

ie., a tag ending in -geometry should automatically behave as if its attach attribute was set to geometry. Same for material.

Also allow the user to set attach="" to override this behavior, in case.

Convert repository into monorepo

Packages:

  • @three-elements/core
  • @three-lements/extras (helpers that are more likely to depend on the latest Three.js)
  • @three-elements/site (private)

IDEA: reference geometries and materials by DOM ID

Similar to how we're allowing the scene to reference the active camera by DOM ID, we could do the same for geometries and materials of meshes (and possibly other properties, too?)

Example:

<three-dodecahedron-buffer-geometry id="shared-geometry" />
<three-mesh geometry="#shared-geometry" />
<three-mesh geometry="#shared-geometry" />

Suggestion: setAnimationLoop for autorender?

Great work Hendrik! ๐ŸŽ‰ I'm a big fan already.

Threejs require the use of setAnimationLoop for WebXR content, so maybe that could be the default when autorender is enabled?

Would also be nice to add the WebXR button as a component. ๐Ÿ˜›

0.3 Release Checklist

  • Go through the documentation and update examples and documentation where necessary
    • Document resource reuse
    • Document custom camera
    • Document custom renderers
    • deg shorthand
  • Test with some of the framework-specific codesandboxes
    • Svelte
    • Vue
    • React
    • Preact
  • Test how it behaves in the lit-shootybang demo
  • Test how it behaves in our... main project
  • Release it to NPM
  • Write blog post on hmans.co

Improve and extend test suite

I would have assume @web/test-runner and @open-wc/testing to be used for testing the web components...

however it seems there are no test ๐Ÿค”

Projected pointer position isn't recalculated when pointer doesn't move

In the pointer-events example, there is a set of 4 polygons rotating about the Y axis. If you hover the mouse pointer over a polygon and then click many times (without moving the pointer) the 3D projected position of the mouse pointer is calculated incorrectly. I believe it may be because it assumes that if the mouse pointer hasn't moved, some part of the calculation can be cached?

Support bracket-less array syntax for "args"

You can set array attributes like this:

position="[0, 5, 10]"

And you may omit the brackets:

position="0, 5, 10"

This is currently not allowed for the args attribute, which you always have to set like this:

args="[1, 2, 3]"

Let's make it so we can also do this:

args="1, 2, 3"

Investigate alternatives to MutationObserver for attributeChanged detection

MutationObserver is potentially expensive if we have many elements on the page, especially if there are faster alternatives. Web Components have a built in mechanism for attribute change detection but it is driven from the observedAttributes static get method.

To use this mechanism we would need a list of all properties of ThreeJS classes at the point we declare the class for each of the matching custom elements. This class is declared in the ThreeElement.for static function. Unfortunately due to the way ThreeJS implementation is constructed we cannot enumerate the properties of the class until after instantiation of an instance of it since many of the class types only define their properties in their constructors.

I spent a little time today investigating potential approaches, given that observedAttributes is evaluated at custom element registration time and we cannot re-trigger this evaluation I have had to rule out a number of different options, this leaves the following potential paths as far as I can see:

Continue using MutationObserver
This probably needs profiling to determine if its acceptable on a scene with many objects, I suspect in a large scene this will be a problem.

Instantiate an instance of each class
In this approach we would instantiate each class and then enumerate the object instances own properties. There are a few downsides with this approach in that some of the ThreeJS apis require parameters to be passed in order to cleanly construct, this will then require a bunch of special case code to handle these dummy instances as well as unwinding any sideeffects creating these dummies would cause.

Use typescript to preprocess the ThreeJS types
This would essentially be a build step that could use the TS compiler API to load the types for the ThreeJS api and build a map of class name to attributes. The TS types fully define the interface to the API and this would then allow us to return the appropriate static list of attributes in the observedAttributes implementation. Downside of course is this requires a build step, and will need to be kept in lcckstep with any Three api changes. Performance however would be good, with a slight payload cost to enumerate all these types and attributes.

Allow setting of "deg(n)" in numerical attributes

What? eg. rotation.x="deg(180)"

Why? Because manually typing out half a PI in 100% static three-elements HTML is a bit meh. (It's not a problem when using frameworks, because those will let you use JavaScript's Math.PI.)

How? We already try to parseFloat the value and then use either that or the string itself. Before using the string, we'll insert another step where we check for a deg(...) value using a regexp.

Allow setting boolean attributes as `cast-shadow` instead of `cast-shadow="true"`

Right now, if the attribute is just cast-shadow, our setup with applyProps will try to assign its value ("") to the castShadow attribute. This is wrong, it should be a boolean! The only way to work around this is to write the attribute as cast-shadow="true".

Some ideas:

  • if the value is "", assume a value of true?
  • check the current type of the property if it's boolean, then see above?
  • ...?

BaseElement.setAttribute should track super.setAttribute succes/failure

Hello

This is a (very) small issue though, but reading this lines in file :

setAttribute(name: string, value: string) {
this.attributeChangedCallback(name, this.getAttribute(name), value)
super.setAttribute(name, value)
}

... I assume wrong arguments can be given (like wrong attributeName) for the native impl. to throw an error.
In this case, the attributeChangedCallback will be called even the actual attribute set is not terminated...

Easy solution: invert the 2 lines, so the callback would not be fired if an error occured.
Less easy solution: Encapsulate the super call in a try/catch and call the callback only in case of success, and manage the error case...

"cast-shadows" should map to "castShadows", not "castshadows" or "castShadows"

What we're doing today:

An attribute named castshadows will result in setting the castShadows property on the object wrapped by the element (since applyProps is currently scanning lower-cased property names as a fallback.)

Hyphens are currently interpreted to "enter" an object, ie. an attribute of foo-bar would set object.foo.bar.

What we should be doing:

  • cast-shadows should set object.castShadows
  • foo:bar should set object.foo.bar

Make onupdate & friends reactive

We're currently handling them imperatively in connectedCallback, but they should really be handled either in applyProps or in a setter so they automatically update when the DOM changes (like all other attributes do.)

Support WebXR Controllers and Events

I'm digging into WebXR since #21, but I'm curious of how we can extend this behavior.

I think we can create elements for controllers, hands, and their events to declaratively manage interactivity. I've made an early demo of these components here but wonder how we can manage events native to WebXR.

Custom camera issue

Hey there, awesome work here.

When i create a custom camera like this:

<three-game>
    <three-scene background-color="#000000" camera="#camera">		
        <three-perspective-camera id="camera" position="0, 0, 100" />
        [...]
    <three-scene />	
<three-game />

It show the next error on browser console:

image

Comming from here:

image

Seems like it is not connected (dont know what that means...). Im trying some stuff with sveltekit, but dont think it is cause of svelte. Anyway, this should work out of the box, isnt?

Also, was wondering if can I switch Camera type on run time? eg. start with orthographic camera and by pressing a button change to perspective. If not, then can I have multiple cameras and set one like the current one?. Also could be good to have a posibility of multiple viewports with the same scene, but i'll let it for later.

Beautiful library.

Directive collision

I'm using a project with Svelte and three-elements and there is a collision when using the ref: directive. Svelte emits a deprecation warning that at the moment cannot be intercepted. The ref directive is no longer supported โ€” use bind:this={material} instead.

I've started an issue in the Svelte repository about being able to intercept these errors, however ref is not uncommon as a directive.

I'd like to put forward two suggestions to give it more flexibility, and avoid collision with other libraries.

  • Prefixing three-elements specific directives would avoid possible collisions with other libraries/frameworks.
  • Expose the function that registers directives so that you are able to put an alias/register another directive with the same function.

Restructure repository; create "extras" package

During the development of 0.4, I converted this repository to a Lerna-based monorepo and started creating individual micropackages for specific features (like text rendering, framework glue, and so on.) I have since realized that this model is not going to scale terribly well (as long as the project is primarily maintained by myself). For this reason, I would like to make the following changes:

  • Create a separate "extras" package that will, for now, house all the various extra stuff implemented on top of three-elements' core package (loaders, text rendering, etc.), similar to how react-drei relates to react-three-fiber
  • House this new package in a separate repository (that may at some point have a different set of maintainers from the core package)
  • move most/all of the various micropackages from this repository to the new package

And ideally I'd like to do all of this before releasing 0.4.

Make connectedCallback, readyCallback, disconnectedCallback less surprising

I've recently realized two important points about connectedCallback:

  • It may be invoked more than once, even if it's just rendered once
  • It may be called once the node is actually not connected

This is because connectedCallback will always be invoked when the element is added to the document, not when it is actually rendered; so a framework that creates the element, adds it to another element, and the moves it somewhere else, for whatever reason, will trigger this.)

Similarly, disconnectedCallback may also be called multiple times.

More details on MDN.

The recommended approach is to check Node.isConnected for the node's current status. We also have .isReady which is set to true after readyCallback is run from ThreeElement's connectedCallback.

So, we need to make this more stable, and less surprising.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.