/*
 * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.sg.prism;

import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import com.sun.javafx.geom.Vec3d;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.prism.Graphics;
import com.sun.prism.Material;
import com.sun.prism.MeshView;
import com.sun.prism.ResourceFactory;

TODO: 3D - Need documentation
/** * TODO: 3D - Need documentation */
public abstract class NGShape3D extends NGNode { private NGPhongMaterial material; private DrawMode drawMode; private CullFace cullFace; private boolean materialDirty = false; private boolean drawModeDirty = false; NGTriangleMesh mesh; private MeshView meshView; public void setMaterial(NGPhongMaterial material) { this.material = material; materialDirty = true; visualsChanged(); } public void setDrawMode(Object drawMode) { this.drawMode = (DrawMode) drawMode; drawModeDirty = true; visualsChanged(); } public void setCullFace(Object cullFace) { this.cullFace = (CullFace) cullFace; visualsChanged(); } void invalidate() { meshView = null; visualsChanged(); } private void renderMeshView(Graphics g) { //validate state g.setup3DRendering(); ResourceFactory rf = g.getResourceFactory(); if (meshView == null && mesh != null) { meshView = rf.createMeshView(mesh.createMesh(rf)); materialDirty = drawModeDirty = true; } if (meshView == null || !mesh.validate()) { return; } Material mtl = material.createMaterial(rf); if (materialDirty) { meshView.setMaterial(mtl); materialDirty = false; } // NOTE: Always check determinant in case of mirror transform. int cullingMode = cullFace.ordinal(); if (cullFace.ordinal() != MeshView.CULL_NONE && g.getTransformNoClone().getDeterminant() < 0) { cullingMode = cullingMode == MeshView.CULL_BACK ? MeshView.CULL_FRONT : MeshView.CULL_BACK; } meshView.setCullingMode(cullingMode); if (drawModeDirty) { meshView.setWireframe(drawMode == DrawMode.LINE); drawModeDirty = false; } // Setup lights int pointLightIdx = 0; if (g.getLights() == null || g.getLights()[0] == null) { // If no lights are in scene apply default light. Default light // is a single point white point light at camera eye position. meshView.setAmbientLight(0.0f, 0.0f, 0.0f); Vec3d cameraPos = g.getCameraNoClone().getPositionInWorld(null); meshView.setPointLight(pointLightIdx++, (float)cameraPos.x, (float)cameraPos.y, (float)cameraPos.z, 1.0f, 1.0f, 1.0f, 1.0f); } else { float ambientRed = 0.0f; float ambientBlue = 0.0f; float ambientGreen = 0.0f; for (int i = 0; i < g.getLights().length; i++) { NGLightBase lightBase = g.getLights()[i]; if (lightBase == null) { // The array of lights can have nulls break; } else if (lightBase.affects(this)) { float rL = lightBase.getColor().getRed(); float gL = lightBase.getColor().getGreen(); float bL = lightBase.getColor().getBlue(); /* TODO: 3D * There is a limit on the number of lights that can affect * a 3D shape. (Currently we simply select the first 3) * Thus it is important to select the most relevant lights. * * One such way would be to sort lights according to * intensity, which becomes especially relevant when lights * are attenuated. Only the most intense set of lights * would be set. * The approximate intesity a light will have on a given * shape, could be defined by: */ // // Where d is distance from point light // float attenuationFactor = 1/(c + cL * d + cQ * d * d); // float intensity = rL * 0.299f + gL * 0.587f + bL * 0.114f; // intensity *= attenuationFactor; if (lightBase instanceof NGPointLight) { NGPointLight light = (NGPointLight)lightBase; if (rL != 0.0f || gL != 0.0f || bL != 0.0f) { Affine3D lightWT = light.getWorldTransform(); meshView.setPointLight(pointLightIdx++, (float)lightWT.getMxt(), (float)lightWT.getMyt(), (float)lightWT.getMzt(), rL, gL, bL, 1.0f); } } else if (lightBase instanceof NGAmbientLight) { // Accumulate ambient lights ambientRed += rL; ambientGreen += gL; ambientBlue += bL; } } } ambientRed = saturate(ambientRed); ambientGreen = saturate(ambientGreen); ambientBlue = saturate(ambientBlue); meshView.setAmbientLight(ambientRed, ambientGreen, ambientBlue); } // TODO: 3D Required for D3D implementation of lights, which is limited to 3 while (pointLightIdx < 3) { // Reset any previously set lights meshView.setPointLight(pointLightIdx++, 0, 0, 0, 0, 0, 0, 0); } meshView.render(g); } // Clamp between [0, 1] private static float saturate(float value) { return value < 1.0f ? ((value < 0.0f) ? 0.0f : value) : 1.0f; } public void setMesh(NGTriangleMesh triangleMesh) { this.mesh = triangleMesh; meshView = null; visualsChanged(); } @Override protected void renderContent(Graphics g) { if (!Platform.isSupported(ConditionalFeature.SCENE3D) || material == null || g instanceof com.sun.prism.PrinterGraphics) { return; } renderMeshView(g); } // This node requires 3D graphics state for rendering @Override boolean isShape3D() { return true; } @Override protected boolean hasOverlappingContents() { return false; } @Override public void release() { // TODO: 3D - Need to release native resources // material, mesh and meshview have native backing that need clean up. } }