Предоставление изображения в виде node в Three.js с SVGRenderer (или иным образом рендеринг сфер)

У меня есть элемент <circle> в документе SVG, к которому я применяю <radialGradient>, чтобы создать иллюзию того, что он является сферой:

<svg version="1.1" id="sphere_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="640px" height="640px" viewBox="0 0 640 640" enable-background="new 0 0 640 640" xml:space="preserve">
    <defs>
        <radialGradient id="sphere_gradient" cx="292.3262" cy="287.4077" r="249.2454" fx="147.7949" fy="274.5532" gradientTransform="matrix(1.0729 0 0 1.0729 -23.3359 -23.3359)" gradientUnits="userSpaceOnUse">
            <stop id="sphere_gradient_0" offset="0" style="stop-color:#F37D7F"/>
            <stop id="sphere_gradient_1" offset="0.4847" style="stop-color:#ED1F24"/>
            <stop id="sphere_gradient_2" offset="1" style="stop-color:#7E1416"/>
        </radialGradient>
    </defs>
    <circle fill="url(#sphere_gradient)" cx="320" cy="320" r="320"/>
</svg>

Это выглядит примерно так:

red sphere

JSFiddle

Я могу сделать это в контейнере three.js WebGLRenderer, используя Gabe Lerner canvg library:

/* sphere_asset is a div containing the svg element */
var red_svg_html = new String($('#sphere_asset').html()); 
var red_svg_canvas = document.createElement("canvas");
canvg(red_svg_canvas, red_svg_html);
var red_svg_texture = new THREE.Texture(red_svg_canvas);
var red_particles = new THREE.Geometry();
var red_particle_material = new THREE.PointCloudMaterial({ 
    map: red_svg_texture, 
    transparent: true, 
    size: 0.15, 
    alphaTest: 0.10 
});
var red_particle_count = 25;
for (var p = 0; p < red_particle_count; p++) {
    var pX = 0.9 * (Math.random() - 0.5),
        pY = 0.9 * (Math.random() - 0.5),
        pZ = 0.9 * (Math.random() - 0.5),
        red_particle = new THREE.Vector3(pX, pY, pZ);
    red_particles.vertices.push(red_particle);
}
var red_particle_system = new THREE.PointCloud(red_particles, red_particle_material);
scene.add(red_particle_system);

До сих пор так хорошо. Я могу даже программно модифицировать градиент и отображать различные категории частиц:

spheres

Теперь мне нужно переключиться с WebGLRenderer на SVGRenderer, чтобы я мог позволить конечному пользователю установить желаемую ориентацию, а затем экспортировать векторное изображение (SVG или преобразовать в PDF на заднем конце), которые могут использоваться для работы с публикацией.

Используя пример песочницы SVG от three.js в качестве основы для экспериментов, я пробовал пару разных методов и не очень много удачи. Я надеюсь, что у кого-то с опытом работы с three.js могут быть некоторые предложения.

Моя первая попытка состояла в том, чтобы использовать canvg для рендеринга SVG в PNG-изображении, а затем применить это к <image> node:

var red_svg_html = new String($('#sphere_asset').html());
var red_svg_canvas = document.createElement("canvas");
canvg(red_svg_canvas, red_svg_html);
var red_png_data = red_svg_canvas.toDataURL('image/png');
var red_node = document.createElementNS('http://www.w3.org/2000/svg', 'image');
red_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', red_png_data);
red_node.setAttributeNS('http://www.w3.org/2000/svg', 'height', '10');
red_node.setAttributeNS('http://www.w3.org/2000/svg', 'width', '10');
var red_particle_count = 25;
for (var i = 0; i < red_particle_count; i++) {
    var object = new THREE.SVGObject(red_node.cloneNode());
    object.position.x = 0.9 * (Math.random() - 0.5);
    object.position.y = 0.9 * (Math.random() - 0.5);
    object.position.z = 0.9 * (Math.random() - 0.5);
    scene.add(object);
}

В моем окне просмотра нет узлов.

Следующее, что я пробовал, это объект THREE.Sprite, используя подпрограммы canvg и THREE.Texture:

var red_svg_html = new String($('#sphere_asset').html());
var red_svg_canvas = document.createElement("canvas");
canvg(red_svg_canvas, red_svg_html);
var red_svg_texture = new THREE.Texture(red_svg_canvas);
red_svg_texture.needsUpdate = true;
var red_sprite = THREE.ImageUtils.loadTexture(red_png_data);
var red_particle_count = 25;
for (var p = 0; p < red_particle_count; p++) {
    var material = new THREE.SpriteMaterial( { 
        map: red_svg_texture, 
        transparent: true, 
        size: 0.15, 
        alphaTest: 0.10 
    });
    var sprite = new THREE.Sprite( material );
    sprite.position.x = 0.9 * (Math.random() - 0.5),
    sprite.position.y = 0.9 * (Math.random() - 0.5),
    sprite.position.z = 0.9 * (Math.random() - 0.5),
    sprite.scale.set(0.1, 0.1, 0.1);
    scene.add(sprite);
}

Это было немного лучше, потому что я получаю белые непрозрачные прямоугольники, в которых сферы будут отображаться в визуализированном окне просмотра.

Была предпринята третья попытка создать <svg> для гнезда внутри родительского SVG node, который содержит ссылочный <radialGradient> с id #sphere_gradient:

var xmlns = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(xmlns, 'svg');
svg.setAttributeNS(null, 'version', '1.1');
svg.setAttributeNS(null, 'x', '0px');
svg.setAttributeNS(null, 'y', '0px');
svg.setAttributeNS(null, 'width', '640px');
svg.setAttributeNS(null, 'height', '640px');
svg.setAttributeNS(null, 'viewBox', '0 0 640 640');
svg.setAttributeNS(null, 'enable-background', 'new 0 0 640 640');

var defs = document.createElementNS(xmlns, "defs");
var radialGradient = document.createElementNS(xmlns, "radialGradient");
radialGradient.setAttributeNS(null, "id", "sphere_gradient");
radialGradient.setAttributeNS(null, "cx", "292.3262");
radialGradient.setAttributeNS(null, "cy", "287.4077");
radialGradient.setAttributeNS(null, "r", "249.2454");
radialGradient.setAttributeNS(null, "fx", "147.7949");
radialGradient.setAttributeNS(null, "fy", "274.5532");
radialGradient.setAttributeNS(null, "gradientTransform", "matrix(1.0729 0 0 1.0729 -23.3359 -23.3359)");
radialGradient.setAttributeNS(null, "gradientUnits", "userSpaceOnUse");

var stop0 = document.createElementNS(null, "stop");
stop0.setAttributeNS(null, "offset", "0");
stop0.setAttributeNS(null, "stop-color", "#f37d7f");
radialGradient.appendChild(stop0);

var stop1 = document.createElementNS(null, "stop");
stop1.setAttributeNS(null, "offset", "0.4847");
stop1.setAttributeNS(null, "stop-color", "#ed1f24");
radialGradient.appendChild(stop1);

var stop2 = document.createElementNS(null, "stop");
stop2.setAttributeNS(null, "offset", "1");
stop2.setAttributeNS(null, "stop-color", "#7e1416");
radialGradient.appendChild(stop2);

defs.appendChild(radialGradient);

svg.appendChild(defs);

var red_circle = document.createElementNS(xmlns, "circle")
red_circle.setAttribute('fill', 'url(#sphere_gradient)');
red_circle.setAttribute('r', '320');
red_circle.setAttribute('cx', '320');
red_circle.setAttribute('cy', '320');
svg.appendChild(red_circle);

var red_particle_count = 25;
for (var i = 0; i < red_particle_count; i++) {
    var object = new THREE.SVGObject(svg.cloneNode(true));
    object.position.x = 0.85 * (Math.random() - 0.5);
    object.position.y = 0.85 * (Math.random() - 0.5);
    object.position.z = 0.85 * (Math.random() - 0.5);
    scene.add(object);
}

Никакие узлы не отображаются. Коррекции элемента <circle> r, cx или cy не изменяют конечный результат.

Интересно, что если я изменяю атрибут fill от url(#sphere_gradient) до red, я получаю большой круг, который в основном отображается вне моего окна просмотра, который не привязан к сцене (он не вращается с другими элементами в моем родительская сцена, как стороны куба).

Существует ли (рабочий и исполнительный) способ рисования сфер или закругленных сферических частиц в пространстве с помощью SVGRenderer в three.js?

Ответ 1

Большинство атрибутов в SVG находятся в нулевом пространстве имен, поэтому

red_node.setAttributeNS('http://www.w3.org/2000/svg', 'height', '10');
red_node.setAttributeNS('http://www.w3.org/2000/svg', 'width', '10');

правильно записывается как

red_node.setAttribute('height', '10');
red_node.setAttribute('width', '10');

FWIW

red_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', red_png_data);

в идеале должно быть записано как

red_node.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', red_png_data);

хотя в этом случае ваша оригинальная форма будет работать в большинстве УА.

В последнем примере элементы остановки должны находиться в пространстве имен SVG i.e.

var stop0 = document.createElementNS(null, "stop");

должен быть

var stop0 = document.createElementNS(xmlns, "stop");

Градиент без остановок вообще не рисуется, поэтому вы ничего не видите, пока не измените заливку на красный.

У SVG есть модель живописцев. Вещи рисуются в порядке их появления в файле. Если вам нужно что-то сделать поверх чего-то другого, вам нужно поместить его позже в файл.

Ответ 2

В вашей первой попытке, если вы добавите библиотеку snap svg, вы можете получить элементы svg из canvg и обернуть их в (как документ для привязки), а затем используйте метод paper.toString() для создания кода svg для вашего svg файла.

var paper = Snap("#... ");        //this wraps the svg element from the canvg onto snap structure 
console.log( paper.toString());   // this gives you the svg code!

Изменить: Или вы можете использовать fabric.js для преобразования canvas в svg:

var svg = canvas.toSVG();

Или вы можете использовать библиотеку canvas2svg.

Ответ 3

Вот что вам нужно добавить в SVGRenerer.js, чтобы исправить вашу проблему с рендерингом Particles:

function renderParticle( v1, element, material, scene ) {


        _svgNode = getCircleNode( _circleCount++ );
        _svgNode.setAttribute( 'cx', v1.x );
        _svgNode.setAttribute( 'cy', v1.y );
        _svgNode.setAttribute( 'r', element.scale.x * _svgWidthHalf );

        if ( material instanceof THREE.ParticleCircleMaterial ) {

            if ( _enableLighting ) {

                _color.r = _ambientLight.r + _directionalLights.r + _pointLights.r;
                _color.g = _ambientLight.g + _directionalLights.g + _pointLights.g;
                _color.b = _ambientLight.b + _directionalLights.b + _pointLights.b;

                _color.r = material.color.r * _color.r;
                _color.g = material.color.g * _color.g;
                _color.b = material.color.b * _color.b;

                _color.updateStyleString();

            } else {

                _color = material.color;

            }

            _svgNode.setAttribute( 'style', 'fill: ' + _color.__styleString );

        }

        _svg.appendChild( _svgNode );


    }